使用用户指定的替换字符串进行 sed 替换
sed substitution with user-specified replacement string
substitution command in sed 的一般形式是:
s/regexp/replacement/flags
其中“/”字符可以统一替换为任何其他单个字符。但是,当替换字符串由环境变量输入并且可能包含任何可打印字符时,您如何选择此分隔符?有没有一种直接的方法可以使用 bash
?
来转义变量中的分隔符?
这些值来自受信任的管理员,因此安全性不是我主要关心的问题。 (换句话说,请不要回答:"Never do this!")不过,我无法预测替换字符串中需要出现哪些字符。
您也可以像这样使用控制字符作为正则表达式分隔符:
s^Aregexp^Areplacement^Ag
其中^A
是CTRLva压在一起
否则使用 awk
并且不用担心分隔符:
awk -v s="search" -v r="replacement" '{gsub(s, r)} 1' file
对于以下使用 sed
.
的问题,这不是(简单的)解决方案
while read -r string from to wanted
do
echo "in [$string] want replace [$from] to [$to] wanted result: [$wanted]"
final=$(echo "$string" | sed "s/$from/$to/")
[[ "$final" == "$wanted" ]] && echo OK || echo WRONG
echo
done <<EOF
=xxx= xxx === =====
=abc= abc /// =///=
=///= /// abc =abc=
EOF
打印什么
in [=xxx=] want replace [xxx] to [===] wanted result: [=====]
OK
in [=abc=] want replace [abc] to [///] wanted result: [=///=]
sed: 1: "s/abc/////": bad flag in substitute command: '/'
WRONG
in [=///=] want replace [///] to [abc] wanted result: [=abc=]
sed: 1: "s/////abc/": bad flag in substitute command: '/'
WRONG
无法抗拒:永远不要这样做!(使用 sed)。 :)
Is there a straightforward way to escape the separator character in
the variable using bash?
不,因为你从变量传递字符串,你不能轻易转义分隔符,因为在 "s/$from/$to/"
中,分隔符不仅可以出现在 $to
部分,还可以出现在 $from
部分也。例如。当您在 $from
部分转义分隔符时,它根本不会进行替换,因为找不到 $from
.
解决方案:使用其他东西作为 sed
1.) 使用纯 bash。在上面的脚本中,使用
而不是 sed
final=${string//$from/$to}
2.) 如果 bash 的替换不够,请使用一些可以传递给 $from
和 $to
作为变量的东西。
正如@anubhava 已经说过的,可以使用:awk -v f="$from" -v t="$to" '{gsub(f, t)} 1' file
或者您可以使用 perl
并将值作为环境变量传递
final=$(echo "$string" | perl_from="$from" perl_to="$to" perl -pe 's/$ENV{perl_from}/$ENV{perl_to}/')
- 或者通过命令行参数将变量传递给 perl
final=$(echo "$string" | perl -spe 's/$f/$t/' -- -f="$from" -t="$to")
2 个选项:
1) 取一个不在字符串中的字符(需要对内容检查和可能的字符进行预处理,但不保证字符可用)
# Quick and dirty sample using `'/_#@|!%=:;,-` arbitrary sequence
Separator="$( printf "%sa%s%s" '/_#@|!%=:;,-' "${regexp}" "${replacement}" \
| sed -n ':cycle
s/\(.\)\(.*a.*.*\)//g;t cycle
s/\(.\)\(.*a.*\)//g;t cycle
s/^\(.\).*a.*//p
' )"
echo "Separator: [ ${Separator} ]"
sed "s${Separator}${regexp}${Separator}${replacement}${Separator}flag" YourFile
2) 转义字符串模式中需要的字符(需要预处理转义字符)。
# Quick and dirty sample using # arbitrary with few escape security check
regexpEsc="$( printf "%s" "${regexp}" | sed 's/#/\#/g' )"
replacementEsc"$( printf "%s" "${replacement}" | sed 's/#/\#/g' )"
sed 's#regexpEsc#replacementEsc#flags' YourFile
来自man sed
\cregexpc
Match lines matching the regular expression regexp. The c may be any
character.
在处理路径时,我经常使用 #
作为分隔符:
sed s\#find/path#replace/path#
不需要用丑陋的 \/
来逃避 /
。
substitution command in sed 的一般形式是:
s/regexp/replacement/flags
其中“/”字符可以统一替换为任何其他单个字符。但是,当替换字符串由环境变量输入并且可能包含任何可打印字符时,您如何选择此分隔符?有没有一种直接的方法可以使用 bash
?
这些值来自受信任的管理员,因此安全性不是我主要关心的问题。 (换句话说,请不要回答:"Never do this!")不过,我无法预测替换字符串中需要出现哪些字符。
您也可以像这样使用控制字符作为正则表达式分隔符:
s^Aregexp^Areplacement^Ag
其中^A
是CTRLva压在一起
否则使用 awk
并且不用担心分隔符:
awk -v s="search" -v r="replacement" '{gsub(s, r)} 1' file
对于以下使用 sed
.
while read -r string from to wanted
do
echo "in [$string] want replace [$from] to [$to] wanted result: [$wanted]"
final=$(echo "$string" | sed "s/$from/$to/")
[[ "$final" == "$wanted" ]] && echo OK || echo WRONG
echo
done <<EOF
=xxx= xxx === =====
=abc= abc /// =///=
=///= /// abc =abc=
EOF
打印什么
in [=xxx=] want replace [xxx] to [===] wanted result: [=====]
OK
in [=abc=] want replace [abc] to [///] wanted result: [=///=]
sed: 1: "s/abc/////": bad flag in substitute command: '/'
WRONG
in [=///=] want replace [///] to [abc] wanted result: [=abc=]
sed: 1: "s/////abc/": bad flag in substitute command: '/'
WRONG
无法抗拒:永远不要这样做!(使用 sed)。 :)
Is there a straightforward way to escape the separator character in the variable using bash?
不,因为你从变量传递字符串,你不能轻易转义分隔符,因为在 "s/$from/$to/"
中,分隔符不仅可以出现在 $to
部分,还可以出现在 $from
部分也。例如。当您在 $from
部分转义分隔符时,它根本不会进行替换,因为找不到 $from
.
解决方案:使用其他东西作为 sed
1.) 使用纯 bash。在上面的脚本中,使用
而不是sed
final=${string//$from/$to}
2.) 如果 bash 的替换不够,请使用一些可以传递给 $from
和 $to
作为变量的东西。
正如@anubhava 已经说过的,可以使用:
awk -v f="$from" -v t="$to" '{gsub(f, t)} 1' file
或者您可以使用
perl
并将值作为环境变量传递
final=$(echo "$string" | perl_from="$from" perl_to="$to" perl -pe 's/$ENV{perl_from}/$ENV{perl_to}/')
- 或者通过命令行参数将变量传递给 perl
final=$(echo "$string" | perl -spe 's/$f/$t/' -- -f="$from" -t="$to")
2 个选项:
1) 取一个不在字符串中的字符(需要对内容检查和可能的字符进行预处理,但不保证字符可用)
# Quick and dirty sample using `'/_#@|!%=:;,-` arbitrary sequence
Separator="$( printf "%sa%s%s" '/_#@|!%=:;,-' "${regexp}" "${replacement}" \
| sed -n ':cycle
s/\(.\)\(.*a.*.*\)//g;t cycle
s/\(.\)\(.*a.*\)//g;t cycle
s/^\(.\).*a.*//p
' )"
echo "Separator: [ ${Separator} ]"
sed "s${Separator}${regexp}${Separator}${replacement}${Separator}flag" YourFile
2) 转义字符串模式中需要的字符(需要预处理转义字符)。
# Quick and dirty sample using # arbitrary with few escape security check
regexpEsc="$( printf "%s" "${regexp}" | sed 's/#/\#/g' )"
replacementEsc"$( printf "%s" "${replacement}" | sed 's/#/\#/g' )"
sed 's#regexpEsc#replacementEsc#flags' YourFile
来自man sed
\cregexpc Match lines matching the regular expression regexp. The c may be any character.
在处理路径时,我经常使用 #
作为分隔符:
sed s\#find/path#replace/path#
不需要用丑陋的 \/
来逃避 /
。