GNU sed无法与组合字符串中的最后一个换行符匹配

时间:2014-01-18 02:58:56

标签: regex bash sed

问题是,当所有传递的字符串合并为一个以匹配换行符时,如果输入有一个尾随换行符,则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

但是,如果我们没有在regexp中添加尾随换行符,它仍会匹配!

$ 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本身相匹配。如果我正确理解了信息页面,它应该在多行模式下,即当替换具有Mm修饰符时。但事实并非如此。还提到了像(实际上是严重标记)和\'(直单引号)之类的组合,它们应该在多行模式下匹配缓冲区边界,但它们在我的shell中不起作用(GNU bash-1.4) .45)因为它们有特殊的意义。

1 个答案:

答案 0 :(得分:3)

Sed仅在输出时为输出添加一个尾随换行符。在将该行放入模式空间之前有一个切断的换行符。这在信息中有记录页面。点击此处:How sed Works。具体地,

  

当到达脚本的末尾时,除非使用-n选项,否则模式空间的内容将打印到输出流,如果删除了后续换行,则添加回来

也就是说,如果它在没有找到换行符的情况下读取了文件的结尾,那么它只会将整行放在模式空间中(这里没有任何内容),当输出模式空间时,它就赢了添加一个新行(因为在第一个地方没有删除任何内容)。这很容易证明:

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被添加到输出,因为在最后一行的末尾有一个。{/ p>