375

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

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

如何转义字符串,以便将其作为文字替换$REPLACE安全地接受?sed

注意:KEYWORD是一个没有匹配项等的哑子字符串。它不是由用户提供的。

4

14 回答 14

325

警告:这不考虑换行符。有关更深入的答案,请参阅此 SO-question。(谢谢,Ed Morton和 Niklas Peter)

请注意,逃避一切是一个坏主意。Sed 需要对许多字符进行转义才能获得它们的特殊含义。例如,如果您对替换字符串中的数字进行转义,它将变成反向引用。

正如 Ben Blank 所说,替换字符串中只有三个字符需要转义(转义自己,正斜杠表示语句结束,& 表示全部替换):

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'

并可用于:

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 的评论。

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

于 2010-04-24T18:35:19.547 回答
110

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

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

双引号不是问题。

于 2015-04-30T11:40:55.677 回答
52

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

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
于 2009-01-02T18:31:15.427 回答
35

基于 Pianosaurus 的正则表达式,我制作了一个 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
于 2012-05-06T01:46:06.793 回答
19

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

而且,这是执行此操作的简单方法:

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

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

于 2014-01-23T17:39:17.893 回答
12

事实证明你问错了问题。我也问错了问题。错误的原因是第一句话的开头:“在我的bash脚本中......”。

我有同样的问题并犯了同样的错误。如果您使用的是 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"}"
于 2015-12-29T22:51:55.223 回答
1

使用 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
于 2014-11-14T23:34:43.547 回答
0

这是我不久前使用的 AWK 的示例。它是一个打印新 AWKS 的 AWK。AWK 和 SED 相似,它可能是一个很好的模板。

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

它看起来过分了,但不知何故,引号的组合可以使 ' 打印为文字。然后,如果我没记错的话,变量只是用这样的引号括起来:“$ 1”。试试看,让我知道它是如何与 SED 一起工作的。

于 2009-01-02T17:58:37.660 回答
0

这些是我发现的转义码:

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

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

' = \x27
? = \x3f
% = \x25
^ = \x5e
于 2020-04-02T20:56:17.957 回答
-1

不要忘记围绕 " 和 ' 的外壳限制所带来的所有乐趣

所以(在 ​​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"
于 2013-11-22T15:29:11.137 回答
-2

如果情况恰好是您正在生成一个随机密码以传递给sed替换模式,那么您选择要小心随机字符串中的哪一组字符。如果您选择将值编码为 base64 的密码,那么只有一个字符既可以在 base64 中使用,又是sed替换模式中的特殊字符。该字符是“/”,很容易从您生成的密码中删除:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;
于 2016-06-01T02:24:35.720 回答
-2

如果您只是想替换 sed 命令中的变量值,那么只需删除示例:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
于 2017-09-01T06:35:20.697 回答
-2

我对 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用引号括起来以避免外壳扩展并保留制表符或双空格。

额外的管道| sed -e 's:\t:\\t:g'(我喜欢:作为令牌)在\t.

于 2017-09-30T19:41:37.517 回答
-6

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

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt
于 2017-10-06T06:54:20.417 回答