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

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

我有一个脚本,它读取文本流并生成 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

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

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

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

答案1

转义要使用的变量左手边右侧s命令sed(此处$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/"

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

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

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

注意:您不想在字符前添加反斜杠不是有特殊的意义,因为这样做,你最终可能会给予它们有特殊的意义。例如,<+t在 BRE 中没有特殊含义,但是\<\+和在(和 for ,包括 RHS 上)\t的某些实现中具有特殊含义。sed\t

假设您/在命令中用作分隔符sed s并且不启用扩展 RE-r(GNU sed// ssed/ ast)busybox sed-E(BSD、ast最近的 GNU、最近的 busybox) 或PCRE-R( ssed) 或增强 RE-A/ -X( ast) 都有额外的 RE 运算符。

对于 ERE(这些扩展中支持最广泛的),等效项是:

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

sed -E "s/$escaped_lhs/$escaped_rhs/"

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

  • 不要使用echo
  • 引用你的变量
  • 考虑区域设置的影响(尤其是其字符集:重要的是逃跑 sedsed命令在与命令相同的区域设置中运行使用逃脱了sed例如字符串(并使用相同的命令)
  • 不要忘记换行符(在这里您可能想检查是否$lhs包含换行符并采取行动)。

更安全的选择是使用perl而不是sed在环境中传递字符串,并使用\Q/\E perl正则表达式运算符按字面意思获取字符串:

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'

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

在某些 shell 中,您还可以进行转义,而无需求助于外部实用程序。

zsh(此处用于 BRE 转义):

set -o extendedglob
escaped_lhs=${lhs//(#m)[][\\.^$\/&]/\\$MATCH}
escaped_rhs=${rhs//(#m)[\\&\/$'\n']/\\$MATCH}

ksh93

escaped_lhs=${lhs//[][\\.^$\/&]/\\\0}
escaped_rhs=${rhs//[\\&\/$'\n']/\\\0}

fish3.4.0+ 中:

set escaped_lhs (
  string replace -ar -- '[][\\\\/.^$*]' '\\\\$0' "$lhs" |
    string collect --allow-empty
)

set escaped_rhs (
  string replace -ar -- '[\\\\&/'\n']' '\\\\$0' "$rhs" |
    string collect --allow-empty --no-trim-newlines
)

相关内容