我有一个脚本,它读取文本流并生成 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
- 引用你的变量
- 考虑区域设置的影响(尤其是其字符集:重要的是逃跑
sed
sed
命令在与命令相同的区域设置中运行使用这逃脱了sed
例如字符串(并使用相同的命令) - 不要忘记换行符(在这里您可能想检查是否
$lhs
包含换行符并采取行动)。
更安全的选择是使用perl
而不是sed
在环境中传递字符串,并使用\Q
/\E
perl
正则表达式运算符按字面意思获取字符串:
A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'
perl
(默认情况下)不会受到语言环境字符集的影响,因为在上面,它只将字符串视为字节数组,而不关心它们可能代表用户的字符(如果有)。使用,您可以通过将所有命令sed
的区域设置修复为C
with来实现相同的目的(尽管这也会影响错误消息的语言,如果有的话)。LC_ALL=C
sed
在某些 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}
在fish
3.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
)