深入理解grep:从基础用法到日志排查实战
2026/9/7 15:24:59 网站建设 项目流程

“每天一个Linux命令”系列:grep,这可能是你最常用的排查工具

老规矩,今天这个系列轮到 grep。

我最早接触 Linux 的时候,其实不太理解为什么大家天天把 grep 挂在嘴边。直到有次线上服务出问题,我需要在几万行日志里定位一条报错,在几十个进程里确认某个服务是否还活着,在配置文件堆里搜某个参数到底写在哪,才发现这玩意几乎是所有排查动作的地基。后来我带新人,第一个星期只让他们练三件事:grep、管道、vim 看文件。这三件事熟练了,Linux 日常操作基本不会卡壳。

这篇不打算把 man 手册抄一遍,而是按照我自己的使用习惯,把 grep 真正高频的用法、组合方式、以及踩过的坑整理出来。无论你是刚入行的运维、写代码的开发、还是准备面试的学生,这都应该能成为你顺手就能翻的参考。如果你已经用了很久 grep,重点可以看最后两章——自匹配的坑和几个容易被忽略的细节,我自己在这上面吃过不少亏。

1. grep 命令定位:它不是“搜索文件”,而是“按模式过滤文本”

1.1 从名字理解 grep 的底层逻辑

grep 这个名字看着奇怪,其实是一个古老缩写:g/re/p,也就是 global regular expression print,全局正则表达式打印。这个名字基本把它做的事情说透了:读取输入,逐行去匹配正则表达式,匹配上的行就打印出来,匹配不上的就丢掉。

理解“逐行处理”这一点很关键。grep 不是把整个文件一次性读进内存再去翻,而是一行一行地读,处理完一行就输出一行。这也是为什么 grep 能轻松处理几个 GB 的大日志文件而不至于把内存打满,因为它的工作方式决定了内存占用基本是固定的。你可以把它想象成流水线上的安检员,每一行文本就是一个人,只有符合规则的才放过去,其他的直接走旁路。

单看文件名,可能会以为它只能搜“文件内容”,但实际它的输入源有三种:直接给定文件名、从标准输入读取、和其他命令用管道拼接。第三种才是它在 Linux 世界里威力最大的用法,后面会专门讲。

1.2 五个出现频率最高的参数,先记这五个就够了

grep 的参数很多,但日常最常用的基本就是下面这几个,我按使用频率排序:

  • -i:忽略大小写。查错误日志时,Error、ERROR、error 都有可能出现,不加这个参数很容易漏。
  • -v:反向匹配,也就是“不含某个关键词的行”,相当于排除。
  • -n:显示行号,查配置、查代码时几乎必用,定位全靠它。
  • -c:统计匹配行数,而不是输出内容本身。想看某个关键字在文件里出现了多少行,用它。
  • -w:按单词匹配,避免“user”把“username”“user_id”也带出来。

举个例子,排查 Nginx 配置里所有不含注释的行:

grep -v '#' /etc/nginx/nginx.conf

再比如统计一个日志文件里出现过多少次 Timeout:

grep -c 'Timeout' app.log

-c统计的是“行数”而不是“次数”。同一行里 Timeout 出现三次,-c也只会计数一次。如果你确实要统计“出现次数”,那得用grep -o 'Timeout' app.log | wc -l-o可以把每个匹配内容单独打一行,再用wc -l数行数。这个组合在统计 IP 访问次数时特别管用。

1.3 上手实例:5 分钟把它用起来

先别急着啃正则,基础搜索就直接上。假设我要在今天所有的日志文件里找一条包含“OutOfMemoryError”的记录:

grep -n 'OutOfMemoryError' /var/log/app/2024-06-18.log

如果当天文件被分割成多个,还可以用通配符指定目录下的一批文件:

grep -n 'OutOfMemoryError' /var/log/app/2024-06-18*.log

这一步的输出会自动带上文件名前缀,比如/var/log/app/2024-06-18-10.log:42:xxx,方便你一眼看出结果落在哪个文件、哪一行。到了这一步,grep 的第一层用法你已经会了,接下来要进阶的是正则和组合。

2. 正则表达式:从“按词找”到“按规律找”

2.1 基础正则与扩展正则,为什么要区分

grep 默认使用的是基础正则表达式(BRE),而egrep或者grep -E使用的是扩展正则表达式(ERE)。两者的区别不在于谁更“高级”,而在于一些元字符需不需要转义。

在基础正则里,+?|{}()这些字符默认是字面量,想让它表达“一个或多个”“可选”“或者”“分组”这些含义,必须加上反斜杠。比如a\+表示一个或多个 a。在扩展正则里,这些符号直接就能用,不需要加反斜杠,写起来自然更舒服。

所以我个人的习惯是:只要涉及正则,一律用grep -E,省得纠结转义。很多人可能觉得默认的 grep 就够了,但如果哪天看到一个命令里写着\{2,\}这种写法,不要觉得奇怪,那就是基础正则下的次数匹配。

2.2 高频正则在真实场景里的写法

日常最常碰到的正则需求其实很固定,我把常用的列出来,每个配一个小例子:

需求正则写法示例说明
行首匹配^grep '^#' config.conf找出所有注释行
行尾匹配$grep 'error$' app.log找以 error 结尾的行
任意单字符.grep 'a.c' file可以匹配 abc、adc、a1c
字符集合[abc][0-9][a-z]grep '[0-9]\{4\}' a.loggrep -E '[0-9]{4}' a.log匹配四位数字
一个或多个+(扩展正则)grep -E 'ab+c' file匹配 abc、abbc
零个或多个*grep 'ab*c' file匹配 ac、abc、abbc
零个或一个?grep -E 'ab?c' file匹配 ac、abc
或逻辑|(BRE)/ ``(ERE)
分组\(\)(BRE)/()(ERE)`grep -E '(error

举个我真实用过的场景。有次排查日志里的异常耗时,要找出所有耗时超过 3000 毫秒的请求,日志格式类似cost=1234ms。用扩展正则写:

grep -E 'cost=[3-9][0-9]{3,}ms' access.log

这个表达式的意思很直白:[3-9]是千位从 3 到 9,[0-9]{3,}是后面至少跟着三位数,这样 3000 以上的都会被捞出来。你可能会问 3000 本身呢?[3-9][0-9]{3,}对 3000 能匹配上,因为千位是 3,后面三位 000。如果我还想包含 2000 到 2999,就得再加一类规则,不过实际排查中很少需要一次把边界算得那么严,大范围命中后人工再确认也不迟。

2.3 关于引号,一个很多新手忽略的坑

grep 的模式建议一律用单引号包起来。原因是单引号能让 shell 不对内容做任何展开,里面写什么就是什么。双引号则不同,如果模式里有$、反引号、反斜杠,shell 可能会先解释一遍再传给 grep。

比如你想在日志里搜一个包含$字符的变量名:

grep '$PATH' env.log

如果你用了双引号grep "$PATH" env.log,shell 会先把$PATH展开成环境变量的值,那搜的东西就完全不对了。这个坑很隐蔽,因为查别的文字时双引号和单引号结果都一样,一旦遇到特殊字符就会莫名其妙地搜不到。

顺带一提,如果你搜的是中文内容,在个别老系统上可能出现乱码或搜不到,可以试试先export LC_ALL=C.UTF-8export LANG=en_US.UTF-8再执行 grep。这个问题在纯英文环境的服务器上很少暴露,但一旦遇到,多半就是 locale 设置的事。

3. 进程管理组合拳:ps 管道 grep 和 kill 的完整链路

3.1 热词里的“ps -ef | grep java”到底能查出什么

几乎所有 Linux 相关的搜索热词里都有ps -ef | grep java,它几乎是运维排查进程的默认起手式。先看ps -ef会输出什么:

UID PID PPID C STIME TTY TIME CMD root 1 0 0 Jun10 ? 00:00:08 /sbin/init root 1234 1 0 Jun10 ? 00:00:00 /usr/sbin/sshd -D appuser 5678 1 99 10:30 ? 00:02:31 java -Xmx2g -jar app.jar

列的含义分别是:用户、进程 ID、父进程 ID、CPU 占用率、启动时间、终端、累计 CPU 时间、完整命令。很多人说“用ps -ef | grep java查看启动时间”,其实STIME列显示的只是启动的日期或时刻,默认精度不够高。如果你真想看精确到秒的启动时间,更好的方式是用ps -eo pid,lstart,cmd | grep javalstart会显示完整的启动时间,比如Thu Jun 18 10:30:21 2024

所以正确的用法组合是:

ps -eo pid,lstart,cmd | grep java

这里我不建议用ps -ef | grep java去查启动时间,因为看到的是简化后的时间。先记住这个差异,后面排查进程卡的时长时能少绕路。

3.2 为什么每次 grep 都会多出一个“自己”?

用过ps -ef | grep xxx的人应该都见过这种输出:

root 10086 1 0 10:30 ? 00:00:00 java -jar app.jar root 10112 1 0 10:31 ? 00:00:00 grep --color=auto java

第二条明显不是我们要找的进程,它是 grep 命令自己的进程。原因其实很简单:管道执行时,系统会同时启动psgrep这两个进程,然后 grep 在扫描 ps 的输出时,输入里包含了它自己的命令行信息(因为ps -ef会列出所有进程,包括正在运行的 grep 命令本身),于是把自己也匹配上了。

解决方式有几种,最经典的是用字符类:

ps -ef | grep '[j]ava'

[j]ava能匹配 java,但 grep 自己的命令行里写的是[j]ava,它不会匹配文本形式的[j]ava,只有真正的 java 进程会被列出来。这个小技巧在面试里偶尔会被问到,实际用起来也确实干净。

另一个更直接的方式是pgrep -f java,它是专门为查进程设计的命令,天然规避了自匹配问题。但 pgrep 默认只输出 PID,想看完整命令还是要ps -fp $(pgrep -f java)或者直接pgrep -af java。两种方式我都用,看当时要做什么。

3.3 从查询、过滤到 kill 的完整实操

搜热词里有一条很典型的操作,用ps -e | grep apt列出所有带 apt 字样的进程,然后用 kill 命令一一杀死。这个流程本质就是三件套:

# 第一步:找到目标进程 ps -ef | grep '[a]pt' # 第二步:提取出 PID 列 ps -ef | grep '[a]pt' | awk '{print $2}' # 第三步:把 PID 传给 kill ps -ef | grep '[a]pt' | awk '{print $2}' | xargs kill

如果确定要大开杀戒,最后那个 kill 可以换成kill -9,但我个人强烈建议不要一上来就用-9kill默认发送的是SIGTERM,相当于客气地请进程自己收拾东西离开,Java 进程能有机会执行关闭钩子、释放端口;而kill -9SIGKILL,直接把进程砍了,资源不清理,文件可能写到一半,甚至留下一些孤儿进程和脏数据。

我用一个真实事故说明。那时候我图省事,对一批批量任务进程执行了kill -9,结果其中一个进程正在写一个中间结果文件,内容是写到一半的半个 JSON。下游任务读这个文件直接解析失败,连锁报了十几个告警。从那以后,我的习惯是先kill,等三秒看进程还在不在,再用kill -9。能用kill解决的,绝不上来就-9

还有一个非常容易被忽略的问题:用xargs kill之前,务必先看一遍 PID 列表,确认没有当前正在跑的、不想杀的关键进程。尤其是那种过滤条件比较宽的命令,比如grep apt,很可能连系统里的aptd守护进程也匹配进去了,杀错以后要花更长时间去恢复。

4. 日志排查与代码搜索的高级打开方式

4.1 递归搜索整个目录,怎么搜才不“爆炸”

grep -r可以递归搜索目录下所有文件,比如在项目代码里找一个函数在哪定义:

grep -rn "getUserInfo" src/

-r是递归,-n是显示行号。这样一条命令就能把整个代码库里所有相关位置列出来。但问题来了:如果目录里有node_modules.gitvendorbuild这些巨型目录,grep 会一股脑全扫进去,又慢又杂。

正确的姿势是用--exclude-dir把无关目录排掉:

grep -rn --exclude-dir=node_modules --exclude-dir=.git "getUserInfo" src/

如果只想搜特定类型的文件,用--include限制:

grep -rn --include="*.java" "getUserInfo" src/

只看文件名、不要内容可以用-l,只统计文件数量用-l | wc -l。在特别大的代码仓库里,--include--exclude-dir配合基本是标配,不然很容易扫出一个文件叫*.min.js全是压缩代码,结果挤满屏幕,真正的命中反而被淹没。

4.2 查日志时带上上下文:-A、-B、-C

很多时候光搜出一个“ERROR”根本不够,你得知道这行错误前面发生了什么、后面跟了什么。grep 提供了三个参数:

  • -B 3:显示匹配行的前 3 行(Before)
  • -A 3:显示匹配行的后 3 行(After)
  • -C 3:显示匹配行前后的各 3 行(Context)

日常排查日志我基本只用-C,一次把上下文都带上:

grep -n -C 5 'NullPointerException' app.log

如果错误日志量很大,还可以配合tail -f做实时过滤,看线上新产生的报错:

tail -f app.log | grep --line-buffered -E 'ERROR|Exception'

--line-buffered很关键。不加它时,grep 会先攒一段输出再打印,你在终端上看到的错误会有明显延迟;加上它之后,grep 每匹配一行就立刻输出,实时性完全不一样。这也是我踩过坑才记住的细节,一开始还以为 tail -f 失灵了。

4.3 另一个准确率更高的搜索思路:先 find 再 grep

在大目录里搜内容,直接grep -r是最省事的,但未必是最快的。如果你知道要找的文件大概在哪里,或者想按文件名字先圈定范围,可以先用 find 找到具体文件,再交给 grep。比如在 /opt 下所有.conf文件里搜端口配置:

find /opt -name "*.conf" -type f -exec grep -Hn "listen" {} \;

find 和 grep 搭配的好处是可控性更强。你可以先看 find 找出了哪些文件,确认范围没问题再批量 greek,避免 grep -r 扫到一堆不想看的文件。也可以借助 xargs 把文件列表传给 grep:

find /opt -name "*.yml" -type f | xargs grep -n "password"

不过文件路径里如果带空格,直接| xargs grep可能会出错,更稳妥的是xargs -d '\n' grep -n。个人经验是,路径里有空格的情况虽然不常见,但一旦出现,xargs报错很难一眼看出来,提前写上-d '\n'能省很多事。这也算是被坑过的经验了。

5. 常见问题、避坑经验与排查速查表

5.1 我踩过的几个坑,提前帮你避开

第一个坑是自匹配,前面已经讲过了,解决方式就是[j]avapgrep。第二个坑是 grep 搜出来的二进制文件,终端会提示Binary file xxx matches,但不显示具体内容。如果日志里混入了二进制内容,可以用grep -a强制把二进制当作文本处理,我处理 Java 进程 dump 出来的堆转储时经常用。

第三个坑是编码问题。有次我在日志里搜中文关键字,明明在 vim 里能看到,用 grep 就是搜不到。后来查了一下,是文件编码是 GBK,而终端和 grep 用的 locale 是 UTF-8。解决办法是先把文件转码再搜,或者用iconv -f GBK -t UTF-8 file.log | grep '关键字'。老系统上这问题出现频率不算低,遇到怪事时先别怀疑 grep 坏了,先看看文件编码。

第四个坑是grep慢。大文件加复杂正则时确实会慢,尤其正则写得不合适的时候。比如grep -E '.*error.*'这种,开头的.*会让正则引擎做大量回溯,性能很差。能缩小范围就缩小:用^.*error不如直接error,很多时候 grep 默认就是逐行包含匹配,不需要额外写.*。真遇到超大文件,还可以用LC_ALL=C grep提升速度,因为 C locale 下排序和匹配规则简化了。实测大文件上能快不少。

5.2 常见问题速查表

现象常见原因解决方式
搜不到,但 vim 里能看到文件编码与 locale 不一致先 iconv 转码,或调整 LANG
结果里总是多一个 grep 进程grep 自匹配[j]avapgrep -f
提示 Binary file matches匹配到了二进制文件-a强制文本模式
匹配行太多,终端卡住没有限制输出量追加 `
大文件搜索慢正则回溯或 locale 影响简化正则,加LC_ALL=C
$*等符号搜不出来shell 先展开了变量或通配符用单引号包裹模式
tail -f配 grep 有延迟管道缓冲--line-buffered
忘了排除目录,搜出一堆 node_modules没有用--exclude-dir加排除项后重搜

这里我再补一个细节。grep默认匹配的是包含关系,也就是“这一行里包含某个模式就算命中”。如果你想精确匹配整行,要加-x,或者用^模式$来锚定。比如查进程时ps -ef | grep -x '...'基本用不上,但处理配置文件时,如果只想找某个 key 单独占一行的情况,-x就很有用了。

5.3 grep 在整个命令体系里的位置,顺便聊聊面试高频考点

说了这么多,最后把 grep 放到整个 Linux 命令体系里看一眼。以日志分析为例,经典的组合是grep + sed + awk:grep 负责按模式捞行,sed 负责批量替换和按行提取,awk 负责按列做统计。这三者配合基本能解决 80% 的日常文本处理需求。比如统计某个接口的平均响应时间,可以用:

grep "GET /api/user" access.log | awk '{print $NF}' | awk '{sum+=$1;count++} END {print sum/count}'

很多公司的运维和后端面试题都会围绕这个组合展开,比如“统计日志中每个 IP 出现的次数”“找出耗时最长的请求”等等。核心考点就是考察你能不能把 grep 的筛选、awk 的分列、sort/uniq 的统计组合起来。建议平时多拿手头的日志练手,练熟了面试时基本不用背题。

最后分享一个小技巧:给 grep 加上颜色和行号的习惯

如果让我总结一个最值得长期坚持的习惯,那就是让 grep 默认带颜色。虽然很多发行版默认就会alias grep='grep --color=auto',但如果你发现自己的 grep 输出里没有高亮,可以在~/.bashrc里加上:

alias grep='grep --color=auto'

颜色高亮的好处不是好看,而是在一大屏输出里,命中位置一眼就能扫出来。尤其配合-n显示行号,排查效率能提升一个档次。

到现在为止,grep 的基础用法、正则进阶、进程组合、日志排查、避坑经验都过了一遍。你如果只记住一件事,那就记住:先过滤,再处理。grep 永远是管道的第一道关卡,也是你面对一堆未知信息时最应该先拿出来的工具。下次再遇到“查进程、查日志、查配置”的需求,希望你能直接想到该用哪条命令。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询