问题是,当传递的所有字符串组合成一个以匹配换行符时,如果输入有尾随换行符,则 sed 会出现匹配问题。
一个简单的字符串。
$ echo -en "aa\nbb\ncc\ndd" | hexdump -C
00000000 61 61 0a 62 62 0a 63 63 0a 64 64 |aa.bb.cc.dd|
0000000b
在这种情况下,如果我们需要用空字符而不是换行符包围最后两段文本,它可以正常工作。
$ echo -en "aa\nbb\ncc\ndd" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)$/\1\x00\2\x00\3\x00/; p}' \
| hexdump -C
00000000 61 61 0a 62 62 00 63 63 00 64 64 00 |aa.bb.cc.dd.|
0000000c
但是,如果输入有尾随换行符,则将尾随 \n
附加到替换正则表达式不会使其匹配。
$ echo -en "aa\nbb\ncc\ndd\n" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)\n$/\1\x00\2\x00\3\x00/; p}' \
| hexdump -C
00000000 61 61 0a 62 62 0a 63 63 0a 64 64 0a |aa.bb.cc.dd.|
0000000c
但是,如果我们没有将尾随换行符添加到正则表达式中,它仍然匹配!
$ echo -en "aa\nbb\ncc\ndd\n" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)$/\1\x00\2\x00\3\x00/; p}' \
| hexdump -C
00000000 61 61 0a 62 62 00 63 63 00 64 64 00 0a |aa.bb.cc.dd..|
0000000d
但它似乎只是忽略了输入中的尾随换行符,或者 $
在某种程度上与它本身匹配。我在 sed FAQ on sourceforge 中找到(§ 5.10) sed 在将其放入模式空间之前从行中剥离尾随换行符,甚至在输出中添加尾随换行符,但是,正如从第二个和第三个示例中可以清楚地看到的那样,它并没有做任何一件事。
所以我读了又读,然后又回想起 $
在某种程度上与尾随的 \n
本身相匹配。如果我正确理解信息页面,它应该是在多行模式下,即当替换具有 M
或 m
修饰符时。但事实并非如此。还提到了诸如 \´
(实际上是坟墓标记)和 \'
(直单引号)之类的组合,它们应该在多行模式下匹配缓冲区边界,但它们不起作用在我的 shell (GNU bash-1.4.45) 中,因为它们具有特殊含义。
最佳答案
只有当有一个换行符在将其放入模式空间之前被截断时,Sed 才会在输出中添加尾随换行符。 这在信息中进行了记录页。检查这里:How sed Works 。具体来说,
When the end of the script is reached, unless the -n option is in use, the contents of pattern space are printed out to the output stream, adding back the trailing newline if it was removed.
也就是说,如果它读取了文件末尾而没有找到换行符,它只会将整行放入模式空间(这里没有被截断),并且在输出模式空间时,它不会添加要么换行(因为一开始就没有删除任何内容)。这很容易演示:
vivek@vivek-laptop:~ $ PS1=' $ '
$ cat > /tmp/file
aa
aa $ sed 's/aa/bb/' /tmp/file
bb
bb $
我在第二行后按了 ctrl-d,因此文件末尾没有终止新行。
进行替换时,sed 将读取第一个 aa\n
,删除 \n
,将 aa
放入模式空间,进行替换(模式空间现在为 bb
),输出模式空间,并添加 \n
。因此,输出 bb\n
。
当它读取第二行时,它会寻找换行符或文件结尾来知道何时停止读取当前行。它读取 aa
(不带 \n
),将其放入模式空间中,进行替换并再次输出模式空间。但这次没有添加 \n
,因为在将行添加到模式空间时没有删除任何内容。
解释您的三种情况:
$ echo -en "aa\nbb\ncc\ndd" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)$/\1\x00\2\x00\3\x00/; p}'
在这种情况下,模式空间将为 aa\nbb\ncc\ndd
。这正确匹配您的正则表达式。此外,不会将 \n
附加到输出中(因为最后没有附加任何内容)。
$ echo -en "aa\nbb\ncc\ndd\n" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)\n$/\1\x00\2\x00\3\x00/; p}'
在这种情况下,模式空间将为 aa\nbb\ncc\ndd
。这与您的正则表达式不匹配,因此不会进行任何替换。 \n
附加到输出中。
$ echo -en "aa\nbb\ncc\ndd\n" \
| sed -rn '1h; 2,$ H; ${g; s/^(.*)\n([^\n]+)\n([^\n]+)$/\1\x00\2\x00\3\x00/; p}'
在这种情况下,模式空间将为aa\nbb\ncc\ndd
。这与您的正则表达式匹配。此外,一个 \n
被附加到输出中,因为最后一行的末尾有一个。
关于regex - GNU sed 无法匹配组合字符串中的最后一个换行符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21199459/