【问题标题】:What do I need to quote in sed command lines?我需要在 sed 命令行中引用什么?
【发布时间】:2013-09-16 13:20:49
【问题描述】:

这个网站上有很多关于如何为 sed 转义各种元素的问题,但我正在寻找更一般的答案。我知道我可能想转义一些字符以避免外壳扩展:

Bash:

  • 单引号 [strings] ('') 用于保留引号内每个字符的字面值。 [然而,]单引号不能出现在单引号之间,即使前面有反斜杠。
  • 只有在后面跟着 dollar反引号双引号时,反斜杠才 [在双引号字符串中] strong>、反斜杠换行符。在双引号内,反斜杠后跟这些字符之一时,将从输入流中删除。反斜杠前面没有特殊含义的字符不会被 shell 解释器处理。

sh:(希望你没有历史扩展)

  • 单引号字符串行为:与 bash 相同
  • 用双引号括起来的字符会保留字面值 引号内的所有字符,dollar单引号反斜杠和除外, 启用历史扩展时,感叹号
    • 字符dollar单引号在双引号中保留其特殊含义。
    • 反斜杠仅在后跟以下字符之一时才保留其特殊含义:$'"\ 或换行符。 双引号可以在双引号内引用 引号前加反斜杠。
    • 如果启用,将执行历史扩展,除非出现在双引号中的 感叹号 使用反斜杠转义。 ! 前面的反斜杠没有被移除。

...但这些都不能解释为什么一旦您删除任何转义,它就会停止工作:

sed -e "s#\(\w\+\) #\1\/#g" #find a sequence of characters in a line
#    why? ↑   ↑ ↑     ↑     #replace the following space with a slash.

()/+(或 [,或 ]...)似乎没有任何特殊含义需要转义才能工作.见鬼,即使直接通过 Python 调用命令也会使 sed 无法正常工作,尽管联机帮助页似乎没有详细说明这一点(无论如何,当我搜索反斜杠时也没有。)

$ lvdisplay -C --noheadings -o vg_name,name > test
$ python
>>> import os
>>> #Python requires backslash escaping of \1, even in triple quotes
>>> #lest \1 is read to mean "byte with value 0x01".
>>> output = os.execl("/bin/sed", "-e", "s#(\w+) #\\1/#g", "test")
(Output remains unchanged)
$ python
>>> import os
>>> output = os.execl("/bin/sed", "-e", "s#\(\w\+\) #\\1\/#g", "test")
(Correct output)
$ WHAT THE HELL
Have you tried using jQuery? It's perfect and it does all the things.

【问题讨论】:

    标签: regex bash sed sh


    【解决方案1】:

    FreeBSD @987654321@,也用于 Mac OS X,使用 -E 代替 -r 扩展正则表达式。 因此,要使其可移植,请使用基本的正则表达式。例如,扩展正则表达式模式下的+ 必须在基本正则表达式模式下替换为\{1,\}。 在基本和扩展正则表达式模式下,FreeBSD sed 似乎无法识别\w,必须将其替换为[[:alnum:]_](参见man re_format)。

    # using FreeBSD sed (on Mac OS X)
    
    # output: Hello, world!
    echo 'hello    world' | sed -e 's/h/H/' -e 's/ \{1,\}/, /g' -e 's/\([[:alnum:]_]\{1,\}\)$/\1!/'
    echo 'hello    world' | sed -E -e 's/h/H/' -e 's/ +/, /g' -e 's/([[:alnum:]_]+)$/\1!/'
    echo 'hello    world' | sed -E -e 's/h/H/' -e 's/ +/, /g' -e 's/(\w+)$/\1!/'  # does not work
    
    # find a sequence of characters in a line
    # replace the following space with a slash
    # output: abcd+/abcd+/
    echo 'abcd+ abcd+ ' > test
    python
    import os
    output = os.execl('/usr/bin/sed', '-e', 's#\([[:alnum:]_+]\{1,\}\) #\\1/#g', 'test')
    

    要在 sed 正则表达式中使用单引号,同时为 sed 正则表达式保留外部单引号,您可以连接三个单独的字符串,每个字符串都用单引号括起来以避免可能的 shell 扩展。

    # man bash:
    # "A single quote may not occur between single quotes, even when preceded by a backslash."
    # cf. http://stackoverflow.com/a/9114512 & http://unix.stackexchange.com/a/82757
    # concatenate: 's/doesn'  +  \'  +  't/does not/'
    echo "sed doesn't work for me" | sed -e 's/doesn'\''t/does not/'
    

    【讨论】:

      【解决方案2】:

      如果你想要一个一般性的答案,

      • shell 元字符需要在 shell 中被引用或转义;
      • 如果您想要字面解释,则需要对正则表达式元字符进行转义;
      • 一些正则表达式结构是由反斜杠转义形成的;根据上下文,这些反斜杠可能需要引用。

      所以你有以下场景;

      # Match a literal question mark
      echo '?' | grep \?
      # or equivalently
      echo '?' | grep "?"
      # or equivalently
      echo '?' | grep '?'
      
      # Match a literal asterisk
      echo '*' | grep \\\*
      # or equivalently
      echo '*' | grep "\\*"
      # or equivalently
      echo '*' | grep '\*'
      
      # Match a backreference: any character repeated twice
      echo 'aa' | grep \\\(.\\\)\\1
      # or equivalently
      echo 'aa' | grep "\(.\)\\1"
      # or equivalently
      echo 'aa' | grep '\(.\)\1'
      

      如您所见,单引号可能在大多数情况下最有意义。

      如果您要嵌入到需要自己反斜杠引用的语言中,则必须添加另一组反斜杠,或者避免调用 shell。

      正如其他人所指出的,扩展正则表达式遵循稍微不同的语法,但一般模式是相同的。最重要的是,为了尽量减少来自 shell 的干扰,请尽可能使用单引号。

      对于文字字符,您可以通过使用字符类来避免一些反斜杠。

      echo '*' | grep \[\*\]
      # or equivalently
      echo '*' | grep "[*]"
      # or equivalently
      echo '*' | grep '[*]'
      

      【讨论】:

        【解决方案3】:

        如果我理解正确,您的问题与 bash/sh 无关,而与 sed 默认使用的正则表达式风格有关:BRE

        其他[=任何点、星号、插入符号和美元] BRE 元字符需要反斜杠来赋予它们特殊的含义。原因是 UNIX grep 的最旧版本不支持这些。

        应该对分组(..) 进行转义以赋予其特殊含义。与+ 相同,否则 sed 将尝试匹配它们,因为它们是文字字符串/字符。这就是为什么你的s#\(\w\+\) #...# 应该被转义。替换部分不需要转义,所以:

        sed 's#\(\w\+\) #\1 /#' 
        

        应该可以。

        sed 通常可以选择使用extended regular expressions(现在有?+|(){m,n});例如GNU sed 有-r,那么你的单行可能是:

        sed -r 's#(\w+) #\1 /#'
        

        我在这里粘贴一些示例,可以帮助您了解发生了什么:

        kent$  echo "abcd "|sed 's#\(\w\+\) #\1 /#'
        abcd /
        kent$  echo "abcd "|sed -r 's#(\w+) #\1 /#'                                                                                                                                 
        abcd /
        kent$  echo "(abcd+) "|sed 's#(\w*+) #&/#'
        (abcd+) /
        

        【讨论】:

        • 感谢您指出-r。我希望正则表达式字符默认意味着业务,这就是那个开关给我的。 :)
        • @badp -r 很方便,但它可能会使您的脚本不可移植。我不确定所有 sed 实现是否都有 -r 用于 ERE。
        • OpenBSD sed supports it too,所以我想这对于大多数用途来说应该足够了。
        【解决方案4】:

        您所观察到的是正确的。在使用 basic 正则表达式时,某些字符(如 ?+(){})需要转义。

        引用sed manual:

        基本正则表达式和扩展正则表达式的唯一区别是 在几个字符的行为中:‘?’‘+’、括号和大括号 (‘{}’)。 虽然基本的正则表达式要求这些被转义如果 您希望它们在使用扩展时表现为特殊字符 如果您希望它们匹配正则表达式,则必须将它们转义 文字字符。

        (强调我的。)但是,当使用 extended 正则表达式时,这些不需要转义,除非匹配文字字符(如最后一行中提到的 quoted em> 上面。)

        【讨论】:

        • 不,您不需要转义反向引用。说\1
        • 是的,那是我被 Python 转义搞砸了。 "\1" 变成 '\x01',就像在一个值为 1 的字节中一样。
        猜你喜欢
        • 2021-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-13
        • 1970-01-01
        • 2011-05-12
        相关资源
        最近更新 更多