转义sed replace模式的字符串

在我的bash脚本中,我有一个外部(从用户接收)字符串,我应该在sed模式中使用。

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

我如何转义$REPLACE字符串,以便它被sed安全地接受为文字替换?

KEYWORD是一个没有匹配的哑子字符串。不是用户提供的。

371116 次浏览

下面是我之前用过的一个AWK的例子。它是一个AWK,打印新的AWKS。AWK和SED相似,可能是一个很好的模板。

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

这看起来有点过分,但不知何故,引号的组合可以保持“打印为字面量”。然后,如果我没记错的话,变量只是用引号括起来:“$1”。试试吧,让我知道它对SED的效果如何。

替换子句中只有三个特殊处理的文字字符是/(关闭子句),\(转义字符,反向引用,&c.)和&(在替换中包含匹配)。因此,你所需要做的就是转义这三个字符:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

例子:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar

警告:这使考虑换行。更深入的答案,请参阅这个问题。(谢谢,埃德莫顿 &Niklas彼得)

请注意,逃避一切是一个坏主意。Sed需要许多字符转义到得到,它们的特殊含义。例如,如果转义替换字符串中的一个数字,它将转换为反向引用。

正如Ben Blank所说,在替换字符串中只有三个字符需要转义(转义本身,语句结束时的正斜杠和&对于replace all):

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

如果你需要转义KEYWORD字符串,下面是你需要的:

sed -e 's/[]\/$*.^[]/\\&/g'

And can be used by:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');


# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

记住,如果你使用/以外的字符作为分隔符,你需要用你正在使用的字符替换上面表达式中的斜杠。请参阅PeterJCLaw的评论以获得解释。

编辑:由于以前没有考虑到的一些极端情况,上面的命令已经更改了几次。详细信息请查看编辑历史记录。

基于钢琴龙的正则表达式,我做了一个bash函数,逃脱关键字和替换。

function sedeasy {
sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

下面是如何使用它:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

不要忘记所有的快乐发生在壳的限制'和'

所以(ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar


echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

现在回复有点晚了……但有一种更简单的方法。只需更改分隔符(即分隔字段的字符)。所以,你写s|bar|foo而不是s/foo/bar/

这里有一个简单的方法:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

结果输出没有那个讨厌的DEFINER子句。

使用awk -它更干净:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

sed命令允许你使用其他字符而不是/作为分隔符:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

双引号不是问题。

看来你问错问题了。我也问错了问题。错误的原因是第一句的开头:“In my bash < em > < / em > script…”

我也有同样的问题&犯了同样的错误。如果使用bash,则不需要使用sed来进行字符串替换(并且使用bash内置的替换特性是清洁器)。

而不是像这样,例如:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

你可以专门使用bash特性:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"

如果您正在生成一个随机密码以传递给sed替换模式,那么您将选择小心随机字符串中的哪一组字符。如果你选择一个由base64编码的值组成的密码,那么只有一个字符在base64中是可能的,并且在sed替换模式中也是一个特殊字符。这个字符是“/”,很容易从你生成的密码中删除:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

如果你只是想替换sed命令中的变量值,那么只需删除 例子:< / p >

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test

我有一个改进sedeasy功能,这将打破特殊字符,如制表符。

function sedeasy_improved {
sed -i "s/$(
echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g'
| sed -e 's:\t:\\t:g'
)/$(
echo "$2" | sed -e 's/[\/&]/\\&/g'
| sed -e 's:\t:\\t:g'
)/g" "$3"
}

那么,有什么不同呢?$1$2用引号括起来,以避免shell展开并保留制表符或双空格。

附加管道| sed -e 's:\t:\\t:g'(我喜欢:作为令牌),它转换\t中的制表符。

更简单的方法是预先构建字符串,并将其用作sed的参数

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

以下是我找到的转义码:

* = \x2a
( = \x28
) = \x29


" = \x22
/ = \x2f
\ = \x5c


' = \x27
? = \x3f
% = \x25
^ = \x5e

答案有很多……如果您不介意使用bash函数模式,下面是一个很好的答案。下面的目标是允许将sed与几乎任何参数一起用作关键字 (F_PS_TARGET)或取代 (F_PS_REPLACE)。我们在许多情况下进行了测试,它似乎相当安全。下面的实现对关键字和replace 取代都支持制表符、换行符和单引号。

这里的思想是使用sed来转义另一个sed命令的条目。

代码

F_REVERSE_STRING_R=""
f_reverse_string() {
: 'Do a string reverse.


To undo just use a reversed string as STRING_INPUT.


Args:
STRING_INPUT (str): String input.


Returns:
F_REVERSE_STRING_R (str): The modified string.
'


local STRING_INPUT=$1
F_REVERSE_STRING_R=$(echo "x${STRING_INPUT}x" | tac | rev)
F_REVERSE_STRING_R=${F_REVERSE_STRING_R%?}
F_REVERSE_STRING_R=${F_REVERSE_STRING_R#?}
}


# [Ref(s).: https://stackoverflow.com/a/2705678/3223785 ]
F_POWER_SED_ECP_R=""
f_power_sed_ecp() {
: 'Escape strings for the "sed" command.


Escaped characters will be processed as is (e.g. /n, /t ...).


Args:
F_PSE_VAL_TO_ECP (str): Value to be escaped.
F_PSE_ECP_TYPE (int): 0 - For the TARGET value; 1 - For the REPLACE value.


Returns:
F_POWER_SED_ECP_R (str): Escaped value.
'


local F_PSE_VAL_TO_ECP=$1
local F_PSE_ECP_TYPE=$2


# NOTE: Operational characters of "sed" will be escaped, as well as single quotes.
# By Questor
if [ ${F_PSE_ECP_TYPE} -eq 0 ] ; then
# NOTE: For the TARGET value. By Questor


F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[]\/$*.^[]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
else
# NOTE: For the REPLACE value. By Questor


F_POWER_SED_ECP_R=$(echo "x${F_PSE_VAL_TO_ECP}x" | sed 's/[\/&]/\\&/g' | sed "s/'/\\\x27/g" | sed ':a;N;$!ba;s/\n/\\n/g')
fi


F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R%?}
F_POWER_SED_ECP_R=${F_POWER_SED_ECP_R#?}
}


# [Ref(s).: https://stackoverflow.com/a/24134488/3223785 ,
# https://stackoverflow.com/a/21740695/3223785 ,
# https://unix.stackexchange.com/a/655558/61742 ,
# https://stackoverflow.com/a/11461628/3223785 ,
# https://stackoverflow.com/a/45151986/3223785 ,
# https://linuxaria.com/pills/tac-and-rev-to-see-files-in-reverse-order ,
# https://unix.stackexchange.com/a/631355/61742 ]
F_POWER_SED_R=""
f_power_sed() {
: 'Facilitate the use of the "sed" command. Replaces in files and strings.


Args:
F_PS_TARGET (str): Value to be replaced by the value of F_PS_REPLACE.
F_PS_REPLACE (str): Value that will replace F_PS_TARGET.
F_PS_FILE (Optional[str]): File in which the replacement will be made.
F_PS_SOURCE (Optional[str]): String to be manipulated in case "F_PS_FILE" was
not informed.
F_PS_NTH_OCCUR (Optional[int]): [1~n] - Replace the nth match; [n~-1] - Replace
the last nth match; 0 - Replace every match; Default 1.


Returns:
F_POWER_SED_R (str): Return the result if "F_PS_FILE" is not informed.
'


local F_PS_TARGET=$1
local F_PS_REPLACE=$2
local F_PS_FILE=$3
local F_PS_SOURCE=$4
local F_PS_NTH_OCCUR=$5
if [ -z "$F_PS_NTH_OCCUR" ] ; then
F_PS_NTH_OCCUR=1
fi


local F_PS_REVERSE_MODE=0
if [ ${F_PS_NTH_OCCUR} -lt -1 ] ; then
F_PS_REVERSE_MODE=1
f_reverse_string "$F_PS_TARGET"
F_PS_TARGET="$F_REVERSE_STRING_R"
f_reverse_string "$F_PS_REPLACE"
F_PS_REPLACE="$F_REVERSE_STRING_R"
f_reverse_string "$F_PS_SOURCE"
F_PS_SOURCE="$F_REVERSE_STRING_R"
F_PS_NTH_OCCUR=$((-F_PS_NTH_OCCUR))
fi


f_power_sed_ecp "$F_PS_TARGET" 0
F_PS_TARGET=$F_POWER_SED_ECP_R
f_power_sed_ecp "$F_PS_REPLACE" 1
F_PS_REPLACE=$F_POWER_SED_ECP_R


local F_PS_SED_RPL=""
if [ ${F_PS_NTH_OCCUR} -eq -1 ] ; then
# NOTE: We kept this option because it performs better when we only need to replace
# the last occurrence. By Questor


# [Ref(s).: https://linuxhint.com/use-sed-replace-last-occurrence/ ,
# https://unix.stackexchange.com/a/713866/61742 ]
F_PS_SED_RPL="'s/\(.*\)$F_PS_TARGET/\1$F_PS_REPLACE/'"
elif [ ${F_PS_NTH_OCCUR} -gt 0 ] ; then
# [Ref(s).: https://unix.stackexchange.com/a/587924/61742 ]
F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/$F_PS_NTH_OCCUR'"
elif [ ${F_PS_NTH_OCCUR} -eq 0 ] ; then
F_PS_SED_RPL="'s/$F_PS_TARGET/$F_PS_REPLACE/g'"
fi


# NOTE: As the "sed" commands below always process literal values for the "F_PS_TARGET"
# so we use the "-z" flag in case it has multiple lines. By Quaestor
# [Ref(s).: https://unix.stackexchange.com/a/525524/61742 ]
if [ -z "$F_PS_FILE" ] ; then
F_POWER_SED_R=$(echo "x${F_PS_SOURCE}x" | eval "sed -z $F_PS_SED_RPL")
F_POWER_SED_R=${F_POWER_SED_R%?}
F_POWER_SED_R=${F_POWER_SED_R#?}
if [ ${F_PS_REVERSE_MODE} -eq 1 ] ; then
f_reverse_string "$F_POWER_SED_R"
F_POWER_SED_R="$F_REVERSE_STRING_R"
fi
else
if [ ${F_PS_REVERSE_MODE} -eq 0 ] ; then
eval "sed -i -z $F_PS_SED_RPL \"$F_PS_FILE\""
else
tac "$F_PS_FILE" | rev | eval "sed -z $F_PS_SED_RPL" | tac | rev > "$F_PS_FILE"
fi
fi


}

模型

f_power_sed "F_PS_TARGET" "F_PS_REPLACE" "" "F_PS_SOURCE"
echo "$F_POWER_SED_R"

例子

f_power_sed "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate" "[ ]+|$/,\"\0\""  "" "Great answer (+1). If you change your awk to awk '{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate that concatenation of the final \", \" then you don't have to go through the gymnastics on eliminating the final record. So: readarray -td '' a < <(awk '{ gsub(/,[ ]+/,\"\0\"); print; }' <<<\"$string\") on Bash that supports readarray. Note your method is Bash 4.4+ I think because of the -d in readar"
echo "$F_POWER_SED_R"

如果您只想将参数转义到sed命令

模型

# "TARGET" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 0
echo "$F_POWER_SED_ECP_R"


# "REPLACE" value.
f_power_sed_ecp "F_PSE_VAL_TO_ECP" 1
echo "$F_POWER_SED_ECP_R"

重要的是:如果用于关键字和/或替换取代的字符串包含制表符或换行符,则需要使用&;-z"在你的"sed"命令。更多细节在这里

例子

f_power_sed_ecp "{ gsub(/,[ ]+|$/,\"\0\"); print }' ./  and eliminate" 0
echo "$F_POWER_SED_ECP_R"
f_power_sed_ecp "[ ]+|$/,\"\0\"" 1
echo "$F_POWER_SED_ECP_R"

注意:上面的f_power_sed_ecpf_power_sed函数作为ez_i -创建shell脚本安装轻松!项目的一部分完全免费提供。

sed通常是一团糟,特别是gnu-sedbsd-sed之间的差异

sed端放置某种类型的哨兵可能会更容易,然后快速连接到awk,后者在接受任何ERE正则表达式、转义十六进制或转义八进制方面要灵活得多。

例如,awk中的OFS是真正的替换::

date | sed -E 's/[0-9]+/\xC1\xC0/g' |


mawk NF=NF FS='\xC1\xC0' OFS='\360\237\244\241'
 1  Tue Aug  🤡 🤡:🤡:🤡 EDT 🤡

(测试并确认在BSD-sedGNU-sed上工作-表情符号不是一个拼写错误,这是这4个字节在UTF-8中的映射)

标准建议:使用perl:)

echo KEYWORD > /tmp/test


REPLACE="<funny characters here>"
perl -pi.bck -e "s/KEYWORD/${REPLACE}/g" /tmp/test
cat /tmp/test