【问题标题】:Escape a string for a sed replace pattern为 sed 替换模式转义字符串
【发布时间】:2019-11-15 17:20:09
【问题描述】:

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

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

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

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

【问题讨论】:

  • 如果他们说“/g -e 's/PASSWORD=.*/PASSWORD=abc/g'”,您是否试图避免“小鲍比桌”问题?
  • 如果使用 bash,则不需要 sed。只需使用outputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
  • @destenson 我认为您不应该将这两个变量放在引号之外。 Bash 可以读取双引号内的变量(在您的示例中,空格可能会搞砸)。
  • @CamiloMartin,请参阅我对自己答案的评论。 ${} 内的引号与内的引号不匹配。这两个变量不在引号之外。

标签: string sed escaping


【解决方案1】:

警告:这考虑换行符。如需更深入的答案,请参阅this 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/[]\/$*.^[]/\\&amp;/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 的评论。

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

【讨论】:

  • 值得注意的是,您可以通过不使用正斜杠作为分隔符来避免转义正斜杠。大多数(全部?)版本的 sed 允许您使用任何字符,只要它符合模式: $ echo 'foo/bar' | sed s_/_:_ # foo:bar
  • sed -e 's/(\/\|\\\|&)/\\&/g' 在 OSX 上对我不起作用,但这样做: sed 's/([ \\\/&])/\\&/g' 稍微短一点。
  • 对于搜索模式 KEYWORD,在 GNU sed 中,这里还有 2 个字符 ^,$ 上面没有提到:s/[]\/$*.^|[]/\\&amp;/g
  • @Jesse:已修复。事实上,这是我在第一段中警告的错误。我想我不会实践我所宣扬的。
  • @NeronLeVelu:我不确定我明白你的意思,但是 " 在管道或变量中没有特殊含义。它在运行结果之前由 shell 解析,所以变量内的双引号是安全。例如,尝试在 bash 中运行 A='foo"bar' echo $A | sed s/$A/baz/。双引号的处理方式与它周围的 'foo' 和 'bar' 一样。
【解决方案2】:

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

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

双引号不是问题。

【讨论】:

  • 你仍然需要转义.,否则有特殊含义。我编辑了你的答案。
  • 我刚刚尝试过:sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filesed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' file,但效果不一样。
  • 因为这仅适用于替换,这应该是说: sed 的s 命令(作为替换)允许您使用其他字符而不是 / 作为分隔符。此外,这将是如何在带有斜杠字符的 URL 上使用 sed 的答案。它不回答 OP 问题如何转义用户输入的字符串,该字符串可能包含 /、\,但如果您决定使用它,还可以包含 #。此外,URI 也可以包含#
  • 它改变了我的生活!谢谢!
【解决方案3】:

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

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

【讨论】:

  • 我认为也是换行符。如何转义换行符?
  • 请注意 echo 的默认行为与反斜杠有关。在 bash 中,echo 默认不解释反斜杠转义,这在这里起到了作用。另一方面,在破折号(sh)中, echo 解释反斜杠转义,据我所知,无法抑制这一点。因此,在破折号 (sh) 中,不要使用 echo $x,而是执行 printf '%s\n' $x。
  • 另外,在进行读取时始终使用 -r 选项将用户输入中的反斜杠视为文字。
  • 为了与其他shell的跨平台兼容,关于sed特殊字符的替换请参考这篇文档:grymoire.com/Unix/Sed.html#toc-uh-62
  • @Drux 这三个字符是 replace 子句中唯一的特殊字符。模式子句还有很多特别之处。
【解决方案4】:

基于 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

【讨论】:

  • 谢谢!如果其他人在尝试使用它时遇到语法错误,就像我一样,请记住使用 bash 运行它,而不是 sh
  • 是否有一个函数只是为 sed 转义一个字符串而不是环绕 sed?
  • 嘿,关于使用这样的回声启动管道的一般警告:一些(大多数?)回声的实现采用选项(参见man echo),导致管道在您的参数@ 987654324@ 以破折号开头。相反,您可以使用printf '%s\n' "$1" 开始您的管道。
  • 它不适用于新行,例如" sedeasy "hello world" "hello\n world" "x.txt"
【解决方案5】:

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

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

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

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

【讨论】:

  • 不,&amp; 和 `` 仍然必须转义,分隔符也必须转义,无论选择哪个。
  • 这解决了我的问题,因为我在替换字符串中有“/”字符。谢谢,伙计!
  • 为我工作。正在做的是尝试在即将更改的字符串中转义$,并在替换字符串中保持$的含义。假设我想将$XXX 更改为变量$YYY 的值,sed -i "s|\$XXX|$YYY|g" file 工作正常。
【解决方案6】:

原来你问错问题了。我也问错了问题。错误的原因是第一句话的开头:“在我的 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"}"

【讨论】:

  • 顺便说一句,这里的语法高亮是错误的。外部引号匹配,内部引号匹配。换句话说,看起来$A$B 没有被引用,但实际上没有。 ${} 内的引号与其外的引号不匹配。
  • 您实际上不必引用作业的右侧(除非您想做类似var='has space' 之类的事情)——OUTPUT=${INPUT//"$A"/"$B"} 是安全的。
  • 您实际上不必引用作业的右侧(除非您希望它在现实世界中工作,而不仅仅是作为玩具脚本来展示您的疯狂技能)。我总是尝试引用我不希望 shell 解释的每个变量扩展,除非我有特定的理由不这样做。这样一来,事情就不会经常中断,尤其是在提供新的或意外的输入时。
  • 参见manual:“所有值都经过波浪号扩展、参数和变量扩展、命令替换、算术扩展和引号删除(详见下文)。”即,与双引号中的相同。
  • 如果需要对文件使用 sed 怎么办?
【解决方案7】:

使用 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

【讨论】:

  • awk 的问题在于它与sed -i 没有任何相似之处,99% 的时间都非常方便。
  • 这是朝着正确方向迈出的一步,但 awk 仍会在您的替换中解释一些元字符,因此用户输入仍然不安全。
【解决方案8】:

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

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

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

【讨论】:

    【解决方案9】:

    这些是我找到的转义码:

    * = \x2a
    ( = \x28
    ) = \x29
    
    " = \x22
    / = \x2f
    \ = \x5c
    
    ' = \x27
    ? = \x3f
    % = \x25
    ^ = \x5e
    

    【讨论】:

    • 并非所有sed 方言都接受\x 的十六进制转义。没有什么可以“发现”的;您可以在任何 ASCII 图表中查找字符代码。
    【解决方案10】:

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

    所以(在 ​​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"
    

    【讨论】:

    • 正是我需要的方向,用于转义查找结果,通过谷歌找到,因此可能对某人有帮助 - 以 - sed "s/[&\\\*\\"\'\" 结尾' )(]/\\&/g'
    【解决方案11】:

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

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

    【讨论】:

      【解决方案12】:

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

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

      【讨论】:

        【解决方案13】:

        我对 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中的选项卡。

        【讨论】:

        • 但请参阅我对在管道中使用 echo 的 sedeasy 答案的评论。
        • 管道sedsed 只是愚蠢的;单个sed 实例可以执行任意长且复杂的脚本。
        【解决方案14】:

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

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

        【讨论】:

        • 失败且极其危险,因为 REPLACE 是用户提供的:REPLACE=/ 提供 sed: -e expression #1, char 12: unknown option to `s'
        猜你喜欢
        • 2012-02-17
        • 2015-01-17
        • 2010-11-07
        • 2020-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多