grep 使用中的一些坑

2024-01-02 • 更新于 2025-03-25

换行符和回车符

首先我们知道,类 Unix 系统都习惯使用换行符 \n(LF,Line Feed)换行,Mac OS 9 及之前则使用回车符 \r(CR,Carriage Return),Windows 则使用 \r\n(CR+LF)换行。

而在 HTTP Header 中,也是以 CR+LF 换行。就算世界上大部分服务器托管在 Linux 上,它还是这么用了,别问,问就是规定。

产生不必要的字符就算了,有时真叫人迷惑。当我在处理 Header 时,发现 grep 莫名其妙“无法匹配”。

为了简化问题,打开终端试试:

printf 'ab\r\n'

结果是显然的,为 ab

这个呢:

printf 'ab\r\n' | grep '^a'

废话,还是 ab

别急,这个呢:

printf 'ab\r\n' | grep '^a.*'

如果你输出“正常”,那么恭喜你,不用踩这个坑。但我还是建议看下去。

如果你的结果是空行,哪天遇到,这破问题能卡老半天。

其实仔细分析,如果 grep 不匹配,连空行都不会打印的。

更别说用 $? 检查返回值了,grep 返回值为 0,这说明根本就是匹配成功的。

把这个问题丢给 DeepSeek 或者 GPT-4o,它们会一本正经地胡说八道。

用以下命令查看真实数据:

printf 'ab\r\n' | grep '^a.*' | hexdump -C
00000000  61 62 0d 0a                                       |ab..|
00000004
printf 'ab\r\n' | grep '^a.*' | cat -v
ab^M

因此 grep 确实匹配到了,莫名其妙就对用户不可见了。

如果这个字符串本身不可见,那么问题来了,为什么 printf 'ab\r\n' 可以正常打印?

难道是因为终端有对控制字符的特殊处理,而管道不行?

如果是管道的问题,为什么 printf 'ab\r\n' | cat 可以打印?

我还尝试了不同机器上包括 BSD 和 GNU 的 grep,都打印空行。

好了,揭晓答案。

最终,我注意到,为了方便查找文本,我在所有机器上都设置了 alias grep="grep --color=auto",这样本身很正常,我相信很多人也是这么干的。

问题就在于,grep 会对结果二次处理,用来显示颜色,回车后原来的字符串自然就被覆盖了!

啊,这……

那么问题来了,为什么 hexdump 结果正常?

其实是因为 grep --color=auto 会自动处理颜色,当不与终端输出挂钩时,是不会二次处理颜色的。

比如,printf 'ab\r\n' | grep '^a.*' | cat 就可以得到匹配文本。

当使用 --color=always 时,就会强行处理颜色:

printf 'ab\r\n' | grep --color=always '^.*' | hexdump -C
00000000  1b 5b 30 31 3b 33 31 6d  1b 5b 4b 61 62 0d 1b 5b  |.[01;31m.[Kab..[|
00000010  6d 1b 5b 4b 0a                                    |m.[K.|
00000015

或者:

printf 'ab\r\n' | grep --color=always '^.*' | cat -v
^[[01;31m^[[Kab^M^[[m^[[K

草,破案了……

不要使用 grep -v 来检查文件是否不包含特定字符串

刷到 StackOverflow 上一个回答1

回答没毛病,但是底下这个点评的 Tom Harrison 言之凿凿地说,可以使用 -v 来反转条件……真是如此?

假设有一个文件 test,内容如下:

a
b

先查找文件中不存在的字符串 c 试试:

$ grep c test; echo $?
1
$ ! grep c test; echo $?
0
$ grep -v c test; echo $?
a
b
0

似乎没问题,两种都对。

再查找文件中存在的字符串 a 呢:

$ grep a test; echo $?
a
0
$ ! grep a test; echo $?
a
1
$ grep -v a test; echo $?
b
0

扯犊子了吧?

原因就在于,-v 反转的是查找条件,而不是查找结果。GNU grep 的手册是这么解释 -v 的:

       -v, --invert-match
              Invert the sense of matching, to select non-matching lines.

-v 会匹配那些不包含 a 的行,自然 b 就被查找出来了。

你可以说这人的意思是 -v 可以反转查找条件,问题是这里是 if 语句,要根据结果的条件来判断,然后进行其他操作,明显这人以为 -v! 是一样的效果。这种点评带有很大的误导性。

那么 -v 能不能实现类似的功能呢?其实是可以的,只不过要结合自己的使用情境配合复杂一点的正则罢了,没必要。

多句嘴,StackOverflow 这个网站虽然很有用,但是对用户并不友好。

我连点赞的权限都没有,还有很多人在瞎点评,而且还是错误的。

有时好不容易搜到一个和自己类似问题,然后就有人点评让你去看某些要么屁用没有要么一堆冗余信息的链接,还说不要重复提问 blah blah……贼烦这种伪精英主义。

ShellLinux

本作品根据 署名-非商业性使用-相同方式共享 4.0 国际许可 进行授权。

下载官方旧版本 Chrome

视频相关的命令和脚本