谢谢这个链接我知道怎么做传递包含斜杠的变量作为 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 的解决方案类似perl
,awk
可以通过替代方法避免保护“危险”插值的问题:
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
提醒:(awk
或sed
)&
在重置价值中进行特殊对待;如果您需要一个实际的&
前缀\
(通常必须首先用 shell 引用;这里是 in ''
),并且类似地将实际的前缀加倍\