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

wef*_*wef 2 bash perl awk sed xxd

假设我们在文件中有一些任意文字,我们需要用其他文字替换.

通常情况下,我们只需要达到sed(1)或awk(1)并编写如下代码:

sed "s/$target/$replacement/g" file.txt
Run Code Online (Sandbox Code Playgroud)

但是,如果$ target和/或$替换可能包含对sed(1)敏感的字符(如正则表达式),该怎么办?你可以逃脱它们但是假设你不知道它们是什么 - 它们是武断的,好吗?您需要编写代码以逃避所有可能的敏感字符 - 包括'/'分隔符.例如

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

对于这么简单的问题,这很尴尬.

perl(1)有\ Q ...\E引用,但即便如此也无法应对'/'分隔符$target.

perl -pe "s/\Q$target\E/$replacement/g" file.txt
Run Code Online (Sandbox Code Playgroud)

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

如果没有,我会留在这里,以防它有用.

zdi*_*dim 7

quotemeta,它实现了\Q,压根就你问的是什么

所有不匹配的ASCII字符/[A-Za-z_0-9]/前面都会加一个反斜杠

因为这可能是在shell脚本中,所以问题实际上是shell变量如何以及何时被插值以及Perl程序最终看到的内容.

最好的方法是避免计算插值混乱,而是将这些shell变量正确地传递给Perl单行.这可以通过几种方式完成; 有关详细信息,请参阅此帖

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

#!/bin/bash

# define $target

perl -pe"BEGIN { $patt = shift }; s{\Q$patt}{$replacement}g" "$target" file.txt
Run Code Online (Sandbox Code Playgroud)

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

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

# define $target, etc

perl -s -pe"s{\Q$patt}{$replacement}g" -- -patt="$target" file.txt
Run Code Online (Sandbox Code Playgroud)

--需要标记的论点开始,开关必须来自之前的文件名.

最后,您还可以导出shell变量,然后可以在Perl脚本中使用%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"
Run Code Online (Sandbox Code Playgroud)

这打印

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"
Run Code Online (Sandbox Code Playgroud)

代码在这里被分解为可读性(因此需要\).相同的打印输出.


wef*_*wef 2

又是我!

这是使用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
Run Code Online (Sandbox Code Playgroud)

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

\n编辑:我忘记从 xxd 输出 ( ) 中删除| tr -d '\n',以便模式可以跨越 xxd 的 60 列输出。当然,这依赖于 GNUsed操作很长行的能力(仅受内存限制)。

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

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

  • 当我第一次看到这个答案时,我觉得这个答案很精彩。几分钟后,我意识到它就像钻石一样有缺陷。例如,如果您尝试在包含“$Q”的文件中将“E”更改为“g”,它将更改为“&q”。这是因为`E`是`45`,`g`是`67`,而`$Q`是`2451`,所以,当你执行``s/45/67/``时,你改变了`2451`到“2671”,即“&q”(“26”+“71”)。...我已经发布了解决此问题的答案。 (2认同)