sed解释器从多个表达式输出时出现不可预测的行为



为什么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-1n#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-1n#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"one_answers"g",整个表达式都用双引号(")。此外,分隔符前面没有转义符("\"),分隔符也不是搜索或替换的一部分。让我们尝试一个不同于"#"的分隔符,如"~":

我使用:|sed-e"s~^(/[a-z]/.*/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的另一个实例时匹配?为什么,哦,为什么,当我这样做的时候,我必须使用不同的delimeter(为了不得到"未终止的命令"?

我问的真正原因是:这是sed中的错误吗?或者,这是我不理解的正确行为吗?(如果是,有人能解释为什么这种行为是正确的吗?)我想知道我是否做错了,或者我是否需要一个不同/更好的工具(或者两者兼而有之,它们不必相互排斥)。

如果有人能够证明为什么这种行为是正确的,或者他们能够证明为什么它是一个错误,我会将响应标记为答案我很乐意接受任何关于其他工具或使用sed的不同方法的建议,但这些都不能回答问题。

我必须在其他文本处理程序(如awk、tr等)上做得更好,因为sed在无法解释的结果上花费了我太多时间。

附言:这不是我的PATH操作的完整逻辑。完整的逻辑还完成了对所有具有从"A00-"到"Z99-"值的行的预处理,然后将输出通过管道传输到"sort-u-f"并返回到sed,以删除每行上的相同前缀,并将行("\n")转换回冒号(":")。然后在单行前面加上"export PATH='",并在其后面附加"'"。然后将输出重定向到一个临时文件中。接下来,该临时文件被来源。最后,该临时文件被删除。

/etc/profile脚本还显示排序前后PATH的内容(以防弄乱路径)。

附言:我相信有更好的方法。它最初是一些非常简单的sed操作,后来发展成了你在这里看到的怪物。即使有更好的方法,我仍然需要知道为什么sed会给我这些结果。

sed -e "s#^(/[a-z]/.*/bin)$#A40-1#ig"

未终止,因为外壳程序正试图扩展"$#A"。把你的表达式用单引号来避免这种情况。

表达式

-e "s#n(/[a-z]/.*/bin)n#nA40-1n#ig"

失败,或者没有达到预期效果,因为.与多行表达式中的换行符匹配。检查您的全部输出,A40-处于最开始的阶段。将其更改为

-e "s#n(/[a-z]/[^n]*/bin)n#nA40-1n#ig"

它可能更符合你的期望。这种情况很可能适用于多行修改的大多数问题。

您还可以将语句(每行一条)放入一个独立文件中,并使用sed -f editscript调用sed。这可能会让维护变得更容易一些。

最新更新