代码之家  ›  专栏  ›  技术社区  ›  wef

如何在sed和awk(和perl)中搜索和替换任意文本字符串

  •  2
  • wef  · 技术社区  · 7 年前

    假设我们在一个文件中有一些任意的文本,需要用其他文本替换。

    正常情况下,我们只需要 塞德 (1)或 AWK (1)并编码如下:

    sed "s/$target/$replacement/g" file.txt
    

    但是如果$target和/或$replacement可以包含对 塞德 (一)正则表达式等。你可以逃避他们,但假设你不知道他们是什么——他们是武断的,好吗?您需要编写一些代码来转义所有可能的敏感字符,包括“/”分隔符。如

    t=$( echo "$target" | sed 's/\./\\./g; s/\*/\\*/g; s/\[/\\[/g; ...' ) # arghhh!
    

    对于这样一个简单的问题,这是相当尴尬的。

    珀尔 (1)有Q…\ e引号,但即使这样也无法处理中的“/”分隔符 $target .

    perl -pe "s/\Q$target\E/$replacement/g" file.txt
    

    我刚刚发布了一个答案!!所以我真正的问题是,“有没有更好的方法在sed/awk/perl中进行文字替换?”

    如果没有,我会把这个放在这里,以防有用。

    3 回复  |  直到 7 年前
        1
  •  6
  •   zdim    7 年前

    这个 quotemeta ,实现 \Q ,绝对按你的要求做。

    所有ASCII字符都不匹配 /[A-Za-z_0-9]/ 将以反斜杠开头

    因为这可能是在shell脚本中,所以真正的问题是shell变量如何以及何时被插入,以及Perl程序最终看到了什么。

    最好的方法是避免计算出这种插值混乱,而是将这些shell变量正确地传递给perl-one行程序。这可以通过几种方式实现;请参见 this post 详情。

    或者简单地将shell变量作为参数传递

    #!/bin/bash
    
    # define $target
    
    perl -pe"BEGIN { $patt = shift }; s{\Q$patt}{$replacement}g" "$target" file.txt
    

    从中删除所需参数 @ARGV 并用于 BEGIN 块,因此在运行时之前;然后 file.txt 得到处理。没有必要 \E 在这里的正则表达式中。

    或者,使用 -s switch ,为程序启用命令行开关

    # define $target, etc
    
    perl -s -pe"s{\Q$patt}{$replacement}g" -- -patt="$target" file.txt
    

    这个 -- 需要标记参数的开头,并且开关必须在文件名之前。

    最后,还可以导出shell变量,然后通过 %ENV 但总的来说,我宁愿推荐上述两种方法中的任何一种。


    完整的例子

    #!/bin/bash
    # Last modified: 2019 Jan 06 (22:15)
    
    target="/{"
    replacement="&"
    
    echo "Replace $target with $replacement"
    
    perl -wE'
        BEGIN { $p = shift; $r = shift }; 
        $_=q(ah/{yes); s/\Q$p/$r/; say
    ' "$target" "$replacement"
    

    这张照片

    Replace /{ with &
    ah&yes
    

    我使用了评论中提到的字符。

    另一种方式

    #!/bin/bash
    # Last modified: 2019 Jan 06 (22:05)
    
    target="/{"
    replacement="&"
    
    echo "Replace $target with $replacement"
    
    perl -s -wE'$_ = q(ah/{yes); s/\Q$patt/$repl/; say' \
        -- -patt="$target" -repl="$replacement"
    

    在这里,为了可读性,代码在行上被打断(因此需要 \ )打印输出相同。

        2
  •  1
  •   user000001 jim mcnamara    7 年前

    用awk你可以这样做:

    awk -v t="$target" -v r="$replacement" '{gsub(t,r)}' file
    

    上述预期 t 若要成为正则表达式,请使用可以使用的字符串

    awk -v t="$target" -v r="$replacement" '{while(i=index($0,t)){$0 = substr($0,1,i-1) r substr($0,i+length(t))} print}' file
    

    灵感来自 this post

    请注意,如果替换字符串包含目标,这将无法正常工作。上面的链接也有相应的解决方案。

        3
  •  0
  •   wef    7 年前

    我又来了!

    这里有一个更简单的方法 XXD (1):

    t=$( echo -n "$target" | xxd -p | tr -d '\n')
    r=$( echo -n "$replacement" | xxd -p | tr -d '\n')
    xxd -p file.txt | sed "s/$t/$r/g" | xxd -p -r
    

    …所以我们用十六进制编码原始文本 XXD (1)使用十六进制编码的搜索字符串进行搜索替换。最后我们对结果进行十六进制解码。

    编辑:我忘记删除 \n 从xxd输出( | tr -d '\n' )这样,模式就可以跨越xxd的60列输出。当然,这取决于GNU sed 在很长的线路上操作的能力(仅受内存限制)。

    编辑:这也适用于多行目标,例如

    目标=$'foo\nbar' 替换=$'bar\nfo'

    推荐文章