替换文件中可能包含斜杠的模式的第一次出现

替换文件中可能包含斜杠的模式的第一次出现

谢谢这个链接我知道怎么做传递包含斜杠的变量作为 sed 的模式:

sed "s~$var~replace~g" $file。只需使用单字节字符代替 / 即可。

谢谢这个其他链接我也知道如何仅替换文件中第一次出现的模式(不在一条线上):

sed "0,/$var/s/$var/replacement/" filename 或者 sed 0,/$var/{s/$var/replacement/} filename

但如果我这样做:( sed '0,~$var~s~$var~replacement~' filename 或以 0 开头的任何其他内容,然后没有斜线),我就会收到错误:unknown command: '0'

我怎样才能将两者结合起来?也许通过使用 awk 或 perl 或...?

答案1

尽管:

sed "0,\~$var~s~$var~replacement~"

可用于更改正则表达式分隔符,sed在一般情况下,在代码(或任何其他解释器)代码中嵌入变量扩展是非常不明智的事情。

首先,这里分隔符并不是唯一需要转义的字符。所有正则表达式运算符也需要这样做。

但更重要的是,尤其是对于 GNU 来说sed,这是一个命令注入漏洞。如果 的内容$var不受您的控制,则与向 传递任意数据一样糟糕eval

尝试例如:

$ var='^~s/.*/uname/e;#'
$ echo | sed "0,\~$var~s~$var~replacement~"
Linux

uname命令已运行,幸好这次是无害的。

非 GNUsed实现无法运行任意命令,但可以覆盖任何文件(使用命令w),这实际上同样糟糕。

更正确的方法是$var首先转义有问题的字符:

NL='
'
case $var in
  (*"$NL"*)
    echo >&2 "Sorry, can't handle variables with newline characters"
    exit 1
esac
escaped_var=$(printf '%s\n' "$var" | sed 's:[][\/.^$*]:\\&:g')
# and then:
sed "0,/$escaped_var/s/$escaped_var/replacement/" < file

另一种方法是使用perl

var=$var perl -pe 's/\Q$ENV{var}\E/replacement/g && $n++ unless $n' < file

请注意,我们不会扩展传递$var到的代码内部的内容perl(这将是另一个命令注入漏洞),而是让perl扩展其内容作为其正则表达式处理的一部分(\Q...\E这意味着正则表达式运算符不会被特殊对待)。

如果$var包含换行符,则仅当末尾只有一个时才可能匹配。或者,可以传递该-0777选项,以便将输入作为单个记录而不是逐行处理。

答案2

与 Stephane 的解决方案类似perlawk可以通过替代方法避免保护“危险”插值的问题:

export pattern="anypattern" # or whatever shell quoting is needed
awk 'NR==1,$0~ENVIRON["pattern"] {sub(ENVIRON["pattern"],"replacement")} 1' input
# or gsub if you want multiple matches on the first line with any match

如果数据(可能)很大并且模式(可能)成本很高,awk那么一旦发现一个稍有不同的习惯用法的匹配,也可以避免不必要的匹配:

awk '!x&&$0~ENVIRON["pattern"] {sub(ENVIRON["pattern"],"replacement");x=1} 1' input
# ditto

提醒:(awksed&在重置价值中进行特殊对待;如果您需要一个实际的&前缀\(通常必须首先用 shell 引用;这里是 in ''),并且类似地将实际的前缀加倍\

相关内容