为什么 GNU sed 有时处理用管道输出到另一个 sed 实例的替换与使用多个表达式时不同?
具体来说,对于 msys/mingw 会话,在 /etc/profile 脚本中,我进行了一系列“重新排列”环境变量 PATH 的顺序并删除重复条目的操作。
请注意,虽然通常 sed 会单独处理每一行输入(因此不能轻易地在输入流中替换 '\n',但此 sed 语句将 ':' 替换为 '\n',因此它仍然处理整个输入流就像一行(其中包含 '\n' 字符)。对于同一 sed 实例中的所有 sed 表达式,此行为保持正确(基本上直到您将输出重定向或通过管道传输到另一个程序)。
这是强制性规格:
Windows 7 Professional Service Pack 1
HP Pavilion dv7-6b78us
16 GB DDR3 RAM
MinGW-w64 (x86_64-w64-mingw32-gcc-4.7.1.2-release-win64-rubenvb) mounted on /mingw/
MSYS (20111123) mounted on / and on /usr/
$ uname -a="MINGW32_NT-6.1 CHRIV-L09 1.0.17(0.48/3/2) 2011-04-24 23:39 i686 Msys"
$ which sed="/bin/sed.exe" (it's part of MSYS)
$ sed --version="GNU sed version 4.2.1"
这是 PATH 操作前的内容:
PATH='.:/usr/local/bin:/mingw/bin:/bin:/c/PHP:/c/Program Files (x86)/HP SimplePass 2011/x64:/c/Program Files (x86)/HP SimplePass 2011:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0:/c/si:/c/android-sdk:/c/android-sdk/tools:/c/android-sdk/platform-tools:/c/Program Files (x86)/WinMerge:/c/ntp/bin:/c/GnuWin32/bin:/c/Program Files/MySQL/MySQL Server5.5/bin:/c/Program Files (x86)/WinSCP:/c/Program Files (x86)/Overlook Fing 2.1/bin:/c/Program Files/7-zip:.:/c/Program Files/TortoiseGit/bin:/c/Program Files (x86)/Git/bin:/c/VS10/VC/bin/x86_amd64:/c/VS10/VC/bin/amd64:/c/VS10/VC/bin'
这是 /etc/profile 的摘录(我已经开始 PATH 操作):
set | grep --color=never ^PATH= | sed -e "s#^PATH=##" -e "s#'##g" \
-e "s/:/\n/g" -e "s#\n\(/[^\n]*tortoisegit[^\n]*\)#\nZ95-\1#ig" \
-e "s#\n\(/[a-z]/win\)#\nZ90-\1#ig" -e "s#\n\(/[a-z]/p\)#\nZ70-\1#ig" \
-e "s#\.\n#A10-.\n#g" -e "s#\n\(/usr/local/bin\)#\nA15-\1#ig" \
-e "s#\n\(/bin\)#\nA20-\1#ig" -e "s#\n\(/mingw/bin\)#\nA25-\1#ig" \
-e "s#\n\(/[a-z]/vs10/vc/bin\)#\nA40-\1#ig"
该行中的最后一个 sed 表达式基本上查找以“/c/VS10/VC/bin”开头的行,并在它们前面加上“A40-”,如下所示:
...
/c/si
A40-/c/VS10/VC/bin
A40-/c/VS10/VC/bin/amd64
A40-/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
我喜欢我的 sed 表达式灵活(路径结构发生变化),但我不希望它与以 amd64 或 x86_amd64 结尾的行匹配(这些行前面将有不同的字符串)。所以我将最后一个表达式更改为:
-e "s#\n\(/[a-z]/vs10/vc/bin\)\n#\nA40-\1\n#ig"
这有效:
...
/c/si
A40-/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
然后,(为了匹配与伪代码 "/x/.../bin" 匹配的任何“行”)我将最后一个表达式更改为:
-e "s#\n\(/[a-z]/.*/bin\)\n#\nA40-\1\n#ig"
产生:
...
/c/si
/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
/c/GnuWin32/bin
...
???- sed 没有匹配任何字符 ('.') 任意次数 ('*')在行的中间 ???
但是,如果我将输出通过管道传输到不同的 sed 实例(并补偿 sed 分别处理每个“行”),如下所示:
| sed -e "s#^\(/[a-z]/.*/bin\)$#A40-\1#ig"
我得到:
sed: -e expression #1, char 30: unterminated `s' command
???那怎么没完没了? 它在 s 之后有所有三个 '#' 字符,在第三个 '#' 之后有修饰符 'i' 和 'g',整个表达式用双引号 ('"')。此外,没有转义 ( '\') 紧接在定界符之前,并且定界符不是搜索或替换的一部分。让我们尝试使用与 '#' 不同的定界符,例如 '~':
我使用:| sed -e "s~^(/[az]/.*/bin)$~A40-\1~ig"
而且,我得到:
...
/c/si
A40-/c/VS10/VC/bin
/c/VS10/VC/bin/amd64
/c/VS10/VC/bin/x86_amd64
A40-/c/GnuWin32/bin
...
而且,这是正确的!我唯一改变的是从'#'到'~'的分隔符,它起作用了???
这不是(甚至接近于)sed 第一次对我产生无法解释的结果。
为什么,哦,为什么,sed 不匹配同一实例中的表达式中的语法,但是当管道传输到另一个 sed 实例时匹配?而且,为什么,哦,为什么,当我这样做时我必须使用不同的分隔符(为了不得到“未终止的's'命令”?
我问的真正原因是:这是 sed 中的错误,还是我不理解的正确行为(如果是,有人可以解释为什么这种行为是正确的)? 我想知道我是否做错了,或者我是否需要不同/更好的工具(或两者,它们不必相互排斥)。
如果有人可以证明为什么这种行为是正确的,或者他们可以证明为什么这是一个错误,我会将它标记为答案。 我很乐意接受有关其他工具或使用 sed 的不同方法的任何建议,但这些不会回答问题。
我将不得不在其他文本处理器(如 awk、tr 等)上做得更好,因为 sed 花费了我太多时间来处理无法解释的结果。
PS 这不是我的 PATH 操作的完整逻辑。完整的逻辑还完成了在所有行前面加上从 'A00-' 到 'Z99-' 的值,然后将输出通过管道传输到 'sort -u -f' 并返回到 sed 以删除每行上的相同前缀并将行 ('\n') 回到冒号 (':')。然后将“export PATH='”附加到单行并附加“'”。然后将该输出重定向到一个临时文件中。接下来,获取该临时文件。最后,该临时文件被删除。
/etc/profile 脚本还显示排序前后的 PATH 内容(以防它搞砸了路径)。
PPS 我确信有更好的方法来做到这一点。它从一些非常简单的 sed 操作开始,发展成为你在这里看到的怪物。即使有更好的方法,我仍然需要知道为什么 sed 会给我这些结果。