grep 如何处理 DOS 行尾?

How does grep handle DOS end of line?

我有一个 Windows 文本文件,其中包含一行(以 CRLF 结尾)

aline

以下是几个命令的输出:

[root@panel ~]# grep aline file.txt
aline
[root@panel ~]# grep aline$'\r' file.txt

[root@panel ~]# grep aline$'\r'$'\n' file.txt

[root@panel ~]# grep aline$'\n' file.txt
aline

第一条命令输出正常。我很好奇第二个和第三个输出。为什么它是一个空行?最后的输出,我认为它找不到字符串但实际上找到了,为什么?命令是 运行 on CentOS/bash.

如果输入格式不正确,则行为未定义。

实际上,某些版本的 GNU grep 将 CR 用于内部目的,因此尝试匹配它根本不起作用,或者会产生非常奇怪的结果。

出于并非完全不同的原因,将文字换行符作为正则表达式的一部分传递可能会有一些奇怪的解释,包括但不限于将参数解释为两个单独的模式。 (看看 grep -F 如何从文件中读取,并想象至少有一些实现使用相同的逻辑来解析命令行。)

在宏伟的计划中,理智的解决方案是修复输入,使其成为有效的文本文件,然后再尝试 运行 Unix 面向行的工具。

对于快速而肮脏的解决方案,一些工具具有针对随机二进制输入的明确定义的语义。 Perl 是这方面的模范公民。

bash$ perl -ne 'print if /aline\r$/' <<<$'aline\r'
aline

Awk 也倾向于友好地工作,尽管有多种实现,因此某个地方的某个版本的行为与 AT&T Awk 行为不同的风险更高。

也许还要注意 \r 是行尾 之前 的最后一个字符(DOS 行尾是序列 CR LF,其中 LF 是标准文本文件的 Unix 行终止符)。

在这种情况下 grep 确实匹配字符串 "aline\r" 但您只是看不到它,因为它 被打印的 ANSI 序列 覆盖了颜色。将输出传递给 od -c,您将看到

$ grep aline file.txt
aline
$ grep aline$'\r' file.txt

$ grep aline$'\r' --color=never file.txt
aline
$ grep aline$'\r' --color=never file.txt | od -c
0000000   a   l   i   n   e  \r  \n
0000007
$ grep aline$'\r' --color=always file.txt | od -c
0000000 033   [   0   1   ;   3   1   m 033   [   K   a   l   i   n   e
0000020  \r 033   [   m 033   [   K  \n
0000030

使用 --color=never 您可以看到输出字符串,因为 grep 不会打印出颜色。 \r 只是将光标重置到行首,然后打印出新行,不会覆盖任何内容。但默认情况下 grep 会检查它是否在终端上 运行 或者它的输出是否正在通过管道传输,如果支持,则以颜色打印出匹配的字符串,并且似乎重置颜色然后打印 \n清除行的其余部分

要匹配 \n,您可以使用 -z 选项使空字节成为行分隔符

$ grep -z aline$'\r'$'\n' --color=never file.txt
aline
$ grep -z aline$'\r'$'\n' --color=never file.txt  | od -c
0000000   a   l   i   n   e  \r  \n  [=25=]
0000010
$ grep -z aline$'\r'$'\n' --color=always file.txt | od -c
0000000 033   [   0   1   ;   3   1   m 033   [   K   a   l   i   n   e
0000020  \r 033   [   m 033   [   K  \n  [=25=]
0000031

你的最后一个命令 grep aline$'\n' file.txt 有效,因为 \n 只是一个 word separator in bash, so the command is just the same as grep aline file.txt. Exactly the same thing happened in the 3rd line: grep aline$'\r'$'\n' file.txt To pass a newline you must quote the argument to prevent word splitting

$ echo "aline" | grep -z "aline$(echo $'\n')"
aline

为了演示第 3rd 行的引用效果,我在文件中添加了另一行

$ cat file.txt
aline
another line
$ grep -z "aline$(echo $'\n')" file.txt | od -c
0000000   a   l   i   n   e  \r  \n   a   n   o   t   h   e   r       l
0000020   i   n   e  \n  [=27=]
0000025
$ grep -z "aline$(echo $'\n')" file.txt
aline
another line
$