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

标签 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

但是,如果我们没有将尾随换行符添加到正则表达式中,它仍然匹配!

$ 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) 中,因为它们具有特殊含义。

最佳答案

只有当有一个换行符在将其放入模式空间之前被截断时,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/

相关文章:

javascript - 正则表达式 - 捕获圆括号和方括号内的任何内容

python - 如何将参数从 python 代码传递到 bash 脚本?

unix - awk,sed : How to parse and sum values from a string

bash - 将管道输出传递给测试命令

linux - 通配符 sed 在同一行的其他文本中搜索/删除

java - 限制电子邮件正则表达式中的特定域

php - 如何查看字符串是否与 PHP 正则表达式值数组匹配?

.net - 用于删除由有序字符集组成的嵌套括号的平衡匹配正则表达式是什么?

regex - 使用正则表达式解析 bash 中的分隔数组

linux - shell脚本看不到远程目录中的文件