【问题标题】:Interpret as fixed string/literal and not regex using sed使用 sed 解释为固定字符串/文字而不是正则表达式
【发布时间】:2010-11-09 11:45:48
【问题描述】:

对于grep,有一个固定字符串选项-F (fgrep) 可以关闭搜索字符串的正则表达式解释。 sed 有类似的设施吗?我在那个男人身上找不到任何东西。推荐其他 gnu/linux 工具也可以。

我使用sed 来实现查找和替换功能:sed -i "s/abc/def/g"

【问题讨论】:

  • 令人惊讶的是,没有人回答您将字符串视为字符串而不是正则表达式的实际问题。我现在会用左臂回答这个问题。我真的希望回答问题的人能够阅读问题并回答所提出的问题......

标签: unix sed


【解决方案1】:

你必须使用sed吗?如果你正在编写一个 bash 脚本,你可以这样做

#!/bin/bash

pattern='abc'
replace='def'
file=/path/to/file
tmpfile="${TMPDIR:-/tmp}/$( basename "$file" ).$$"

while read -r line
do
  echo "${line//$pattern/$replace}"
done < "$file" > "$tmpfile" && mv "$tmpfile" "$file"

使用较旧的 Bourne shell(例如 ksh88 或 POSIX sh),您可能没有那么酷的 ${var/pattern/replace} 结构,但您确实有 ${var#pattern}${var%pattern},可用于拆分字符串并然后重新组装它。如果你需要这样做,你需要更多的代码——但这真的还不错。

如果您还没有使用 shell 脚本,您可以很容易地创建模式、替换和文件名参数,然后调用它。 :)

PS:${TMPDIR:-/tmp} 结构使用 $TMPDIR(如果在您的环境中设置),或者使用 /tmp(如果未设置变量)。我喜欢将当前进程的 PID 粘贴在文件名的末尾,希望它会更加独特。您可能应该在“现实世界”中使用 mktemp 或类似名称,但这只是一个简单的示例,而且 mktemp 二进制文件并不总是可用。

【讨论】:

  • 这是一个缓慢的解决方案。文件的每一行都会调用 echo,如果您使用的是大文件或多个文件,这可能会产生数千个 echo 进程。
  • echo 通常被实现为一个 shell 内置函数,它应该在像 bash 这样的现代 shell 上分叉 0 个进程。
  • 您可以使用“strace -e trace=process ./shellscript”查看内置函数。编写上面的脚本,看看有多少个 fork 调用。在当前的 Ubuntu 上使用当前的 Bash,有 2 个 fork(实际上是 clone(),比 fork())调用更快。一个用于执行 basename 的子shell,一个用于 mv。另一方面,如果你使用“/bin/echo”重写它,那么实际上每一行都有一个新的分叉。您可以看到它需要 很多 更长的时间才能运行。 :)
  • 另外,为了避免读取和回显替换 \n-e 之类的内容,您可能想要执行 while read -r line;do printf "%s\n" "${line/$pattern/$replace}";done 之类的操作
  • 该解释中存在不准确之处,大多数原因都是基于(“可变范围很难”并且如果您使用蹩脚的外壳,性能会很差),其余的只是关于“可读性”的意见" - 就像将相同的代码放入循环中突然使其完全难以辨认。 :)
【解决方案2】:

选项 1) 转义正则表达式字符。例如。 sed 's/\$0\.0/0/g' 会将所有出现的 $0.0 替换为 0。

选项 2) 将 perl -p -e 与 quotemeta 结合使用。例如。 perl -p -e 's/\\./,/gi' 会将所有出现的. 替换为,

您可以在这样的脚本中使用选项 2:

SEARCH="C++"
REPLACE="C#"
cat $FILELIST | perl -p -e "s/\\Q$SEARCH\\E/$REPLACE/g" > $NEWLIST

【讨论】:

  • perl 有相当于 sed 的 -i 吗?我使用 sed 如下: find -name "myfiles" |xargs sed -i "s/abc/def/g"
  • 我看到您的 perl 示例被解释为正则表达式!
  • $ echo "a.b.c" | perl -p -e 's/./,/g' 给我 ",,,,," 而不是 "a,b,c"
  • @EoghanM:那是因为.需要转义,否则会匹配任何字符。
  • Perl 确实有一个 -i 参数,其工作方式与 sed -i arg 类似。
【解决方案3】:

如果您不想想要转义字符串,您可以分两步达到目标:

  1. fgrep要替换的行(获取行号),以及
  2. 之后使用sed 替换此行。

例如

#/bin/sh
PATTERN='foo*[)*abc'    # we need it literal
LINENUMBER="$( fgrep -n "$PATTERN" "$FILE" | cut -d':' -f1 )"

NEWSTRING='my new string'
sed -i "${LINENUMBER}s/.*/$NEWSTRING/" "$FILE"

【讨论】:

  • 高效、简洁、到位!
  • 好主意。我们也可以踢出cut 并使用标准的'grep':line_num=$(grep -Fn "$PATTERN" "$FILE"); line_num=${line_num%%:*},它仍然是POSIX
【解决方案4】:

如果你不反对 Ruby 或长线,你可以使用这个:

alias replace='ruby -e "File.write(ARGV[0], File.read(ARGV[0]).gsub(ARGV[1]) { ARGV[2] })"'

replace test3.txt abc def

这会将整个文件加载到内存中,执行替换并将其保存回磁盘。可能不应该用于大量文件。

【讨论】:

  • gsub 在替换字符串中插入反向引用。您将无法将 '\2' 之类的内容替换为 '\1'
  • @Sorin 正确,已修复 :)
【解决方案5】:

如果您可以将整个文件读入内存,则可以在两行 bash 代码中执行此操作。这是非常灵活的——如果需要,模式和替换可以包含换行符以跨行匹配。它还保留任何尾随换行符或缺少的换行符,read 的简单循环不会。

mapfile -d '' < file
printf '%s' "${MAPFILE//"$pat"/"$rep"}" > file

为了完整起见,如果文件可以包含空字节(\0),我们需要对上面进行扩展,它变成了

mapfile -d '' < <(cat file; printf '\0')
last=${MAPFILE[-1]}; unset "MAPFILE[-1]"
printf '%s\0' "${MAPFILE[@]//"$pat"/"$rep"}" > file
printf '%s' "${last//"$pat"/"$rep"}" >> file

【讨论】:

    【解决方案6】:
    perl -i.orig -pse  'while (($i = index($_,$s)) >= 0) { substr($_,$i,length($s), $r)}'--\ 
         -s='$_REQUEST['\'old\'']' -r='$_REQUEST['\'new\'']' sample.txt
    
    • -i.orig 就地修改备份。
    • -p 默认从输入文件打印行
    • -s 启用命令行参数的基本解析
    • -e 运行这个脚本
    • index($_,$s) 搜索 $s 字符串
    • substr($_,$i,length($s), $r)替换字符串
    • while (($i = index($_,$s)) &gt;= 0) 重复直到
    • -- perl 参数结束
    • -s='$_REQUEST['\'old\'']', -r='$_REQUEST['\'new\'']' - 设置$s,$r

    您仍然需要“转义”' 字符,但其余的应该是直截了当的。

    注意:这开始是对How to pass special character string to sed 的回答,因此是$_REQUEST['old'] 字符串,但是这个问题的表述更为恰当。

    【讨论】:

      【解决方案7】:

      您应该使用replace 而不是sed

      来自手册页:

         The replace utility program changes strings in place in files or on the
         standard input.
      
         Invoke replace in one of the following ways:
      
             shell> replace from to [from to] ... -- file_name [file_name] ...
             shell> replace from to [from to] ... < file_name
      
         from represents a string to look for and to represents its replacement.
         There can be one or more pairs of strings.
      

      【讨论】:

      • hmmm,没有安装'replace' - 它看起来是一个与 MySQL 相关的工具?我安装了 MySql;是否有单独的替换安装?
      • 它没有单独包装,抱歉。您可以尝试在 mysql 源代码树中找到它并自己构建它,但这很麻烦。所以你有mysql,但没有/usr/bin/replace?您可以尝试重新安装 mysql 包 (mysql-server)。
      • 不在 /usr/bin 中,无法重新安装!假设它有效,我会接受你的回答:)
      • 由于大多数系统上没有/usr/bin/replace,这对我来说并不是一个普遍的答案。
      • replace 的另一个主要缺陷是它在进行就地编辑时不进行备份。
      猜你喜欢
      • 2016-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-21
      • 1970-01-01
      • 2019-09-18
      相关资源
      最近更新 更多