3

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

重击

  • 单引号 [strings] ('') 用于保留引号内每个字符的文字值。[然而,]单引号之间可能不会出现单引号,即使前面有反斜杠也是如此。
  • 反斜杠仅在后跟美元反引号双引号反斜杠换行符时才保留其含义 [在双引号字符串中] 。在双引号内,反斜杠后跟这些字符之一时,将从输入流中删除。没有特殊含义的字符前面的反斜杠不作修改,以供 shell 解释器处理。

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

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

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

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

(, ), /or +(or [, or ...) 似乎没有]任何特殊含义需要它们被转义才能工作。地狱,即使直接通过 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.
4

4 回答 4

4

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

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

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

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

应该管用。

sed通常可以选择使用扩展的正则表达式(现在使用?, +, |, (), {m,n});例如 GNU sed has -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+) /
于 2013-09-12T08:51:55.027 回答
1

如果你想要一个普遍的答案,

  • 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 '[*]'
于 2013-09-12T09:24:54.083 回答
1
于 2013-09-12T08:52:24.150 回答
-1

FreeBSDsed也用于 Mac OS X,它使用-E而不是-r扩展正则表达式。因此,要使其可移植,请使用基本的正则表达式。+例如,在扩展正则表达式模式中,必须替换为\{1,\}基本正则表达式模式。在基本和扩展正则表达式模式下,FreeBSDsed似乎无法识别\w哪些必须替换为[[:alnum:]_](cf. 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/'
于 2013-09-12T14:53:25.903 回答