如何确保插入到`sed` 替换中的字符串会转义所有元字符

dan*_*dan 25 sed quoting

我有一个脚本,它读取文本流并生成一个 sed 命令文件,该文件稍后使用sed -f. 生成的 sed 命令如下:

s/cid:image002\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1922/g
s/cid:image003\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1923/g
s/cid:image004\.jpg@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1924/g
Run Code Online (Sandbox Code Playgroud)

假设生成sed命令的脚本类似于:

while read cid fileid
do
    cidpat="$(echo $cid | sed -e s/\\./\\\\./g)"
    echo 's/'"$cidpat"'/https:\/\/mysite.com\/files\/'"$fileid"'/g' >> sedscr
done
Run Code Online (Sandbox Code Playgroud)

如何改进脚本以确保cid字符串中的所有正则表达式元字符都被正确转义和插入?

Sté*_*las 27

要转义要ssed(此处$lhs$rhs分别)中的命令的左侧和右侧使用的变量,您可以执行以下操作:

escaped_lhs=$(printf '%s\n' "$lhs" | sed 's:[][\\/.^$*]:\\&:g')
escaped_rhs=$(printf '%s\n' "$rhs" | sed 's:[\\/&]:\\&:g;$!s/$/\\/')

sed "s/$escaped_lhs/$escaped_rhs/"
Run Code Online (Sandbox Code Playgroud)

请注意,$lhs不能包含换行符。

也就是说,在 LHS 上,转义所有正则表达式运算符 ( ][.^$*)、转义字符本身 ( \) 和分隔符 ( /)。

在 RHS 上,您只需要转义&、分隔符、反斜杠和换行符(您可以通过在除最后一行 ( $!s/$/\\/)之外的每一行的末尾插入反斜杠来实现)。

这假设你用/在你的分隔符sed s的命令和你没有启用扩展的RE-r(GNU sed/ ssed/ ast/ busybox sed)或-E(BSD系统,ast最近GNU,最近busybox的)或PCREs-Rssed)或增强的RE-A/ -Xast),它都有额外的 RE 操作符。

处理任意数据时的一些基本规则:

  • 不要使用 echo
  • 引用你的变量
  • 考虑语言环境的影响(尤其是它的字符集:例如,转义 sed命令与sed使用转义字符串(并使用相同sed命令)的命令在相同的语言环境中运行很重要)
  • 不要忘记换行符(在这里您可能想检查是否$lhs包含任何内容并采取行动)。

另一种选择是使用perl代替sed并传递环境中的字符串,并使用\Q/ \E perlregexp 运算符从字面上获取字符串:

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'
Run Code Online (Sandbox Code Playgroud)

perl(默认情况下)不会受到语言环境字符集的影响,因为在上面,它只将字符串视为字节数组,而不关心它们可能代表用户的字符(如果有的话)。使用sed,您可以通过将所有命令的语言环境固定为CwithLC_ALL=C来实现相同的效果sed(尽管这也会影响错误消息的语言,如果有的话)。