Linux grep命令完全指南:从基础搜索到正则与日志分析实战 📅 发布时间:2026/9/18 23:53:04 👁 浏览次数: 工作了十来年我几乎每天都要跟 Linux 打交道。如果让我在所有命令里只能留一个贴身工具那多半就是 grep。你可以在任何一台机器上跑grep --help但说实话很少有人真正把 grep 吃透。很多人只会拿它简单的搜个关键字遇到复杂点的场景就开始绕路要么写脚本循环读文件要么用编辑器慢慢翻效率低不说还容易把问题想复杂。这篇就是一份能把 grep 讲到位的实战笔记。从最基础的用法到正则表达式再到日志分析、进程过滤、递归搜索这些真实场景以及我踩过的一些坑都会覆盖到。不管你是刚接触 Linux 的新手还是已经在用但总觉得差点意思的运维和开发这篇都应该对你有用。建议不要只把下面的命令复制去跑一遍就完事而是每一个都想想这个输出是怎么来的换一行数据结果会怎样想通了grep 就真正长在你身上了。1. grep是什么从最简单的字符串匹配说起1.1 最基本的用法从一行 grep 开始grep 的全称是 Global Regular Expression Print可以简单理解为“全局按正则规则匹配并打印”。这个名字其实已经把它的核心功能说清楚了它会读入文件或标准输入按你给的模式去匹配每一行内容然后把命中的行打印到标准输出。我刚开始接触时最习惯的用法是grep error app.log。这里的字符串没必要带引号不带的写法是grep error app.log一样能跑。但我还是建议养成带引号的习惯。倒不是因为别的而是当你要匹配的内容里出现空格、特殊符号时不带引号十有八九会被 shell 拆开最后报错或者匹配得莫名其妙。你还可以把 grep 用在管道上这也是更日常的姿势cat app.log | grep ERROR这里其实就是 grep 从标准输入读取数据。有人会问既然 grep 可以直接读文件为什么还要cat | grep如果只是过滤单个文件确实没必要多此一举直接grep ERROR app.log就行。但如果你前面接的是tail -f、curl、ps这类命令的输出就得靠管道把它接给grep了。这类用法我会在第3章详细讲。1.2 中断管道、退出码与三个隐藏状态很多人不知道grep 的执行结果其实是有状态的而且这个状态在脚本里特别重要。收到匹配内容时grep 返回 0没有匹配到时返回 1如果文件不存在或者参数写错返回 2。写 Shell 脚本时你可以直接用这个返回值判断本次查找是否有结果不用再去数输出行数。if grep -q ERROR app.log; then echo 日志里有ERROR else echo 没发现ERROR fi这里-q参数的意思是 quiet不输出内容只返回状态码。如果日志文件很大用-q还有一个额外好处grep 在找到第一处匹配后就会提前退出不会再傻乎乎地扫描完整份文件一批就是省时间。这里也顺带提一个新手容易困惑的坑你执行grep xxx file | wc -l想统计行数如果文件里没有匹配内容wc 会输出 0但管道的最后一个命令返回码是 0所以这个命令“成功了”。如果你想判断“是否找到”别数 wc 结果直接检查 grep 那一步的退出码才是正确思路。1.3 高频选项速查-i、-v、-c、-n、-o、-l、-r先上一张实用速查表这些是我认为日常使用频率最高的一批选项。后面章节里会展开讲其中一部分但你先有个整体印象比较好。选项作用典型示例-i忽略大小写grep -i error app.log-v反向匹配输出未命中行grep -v DEBUG app.log-c统计匹配行数而不是输出行grep -c ERROR app.log-n在输出时带上行号grep -n ERROR app.log-o只输出匹配到的部分而不输出整行grep -o [0-9]\ app.log-l只输出包含匹配内容的文件名grep -l TODO src/*.c-r递归搜索目录grep -r main src/-w按单词边界匹配grep -w cat file-x整行完全匹配grep -x 192.168.1.1 hosts-q静默模式只返回状态码grep -q OK file-i有多常用在生产环境排查问题的时候日志里的级别字段可能写成ERROR、Error、error三种都有如果你不写-i就会漏掉一部分然后产生“为什么日志里没有错误”的错觉。类似地grep -i在搜索配置项、环境变量名称时也非常实用。-v的典型场景是过滤掉注释行和空行grep -v ^# /etc/nginx/nginx.conf | grep -v ^$这样看配置文件就清爽多了没有被注释掉的有效配置一眼就能看到。2. 正则表达式让 grep 真正强大的核心2.1 基础正则 vs 扩展正则行首、行尾、任意字符、量词grep 默认使用的是基础正则表达式BRE很多符号都需要转义才能表达特殊含义。而使用扩展正则ERE也就是grep -E或直接使用egrep就不再需要那么多转义了。两个典型的区别# 基础正则写法 grep error[0-9]\ app.log # 扩展正则写法 grep -E error[0-9] app.log用表示“前面的字符出现一次或多次”时基础正则必须写成\扩展正则就正常写这就是为什么很多人更习惯用-E的原因。说实话我平时几乎都用-E它更贴近我脑子里对正则的理解。行首行尾用的符号比较固定^匹配一行的开头$匹配一行的结尾比如想找所有以2025-开头的行grep -E ^2025- app.log想找以ERROR结尾的行grep -E ERROR$ app.log还有一个容易忽略的在基础正则和多数字符环境中.表示“任意一个字符”比如gr.y可以匹配gray、grey、gr5y等等。日志里某个字段可能变化比如订单号前缀相同但后几位不同用.去模糊匹配就比写死一串字符灵活得多。2.2 方括号表达式与字符类灵活但不难方括号[]用来表示“匹配方括号里的任意一个字符”。这背后其实就是“字符集合”的思路。grep -E gr[ae]y file同时包含gray和grey的行都会被匹配出来。如果只想匹配范围可以用-grep -E 202[0-5] app.log这个表达式会匹配年份从 2020 到 2025 的所有行。方括号里还可以取反[^a-z]表示任意一个不是小写字母的字符。我实际用方括号比较多的是过滤可疑 IP 或者固定格式订单号的时候。比如日志里的订单号可能是ORD-000123、ORD-000456这种格式用grep -E ORD-[0-9]就能把所有订单相关行捞出来。另外还有一组 POSIX 字符类在 grep 里直接嵌套在方括号内使用[[:digit:]]等价于[0-9][[:alpha:]]匹配字母[[:alnum:]]匹配字母和数字[[:space:]]匹配空白字符[[:upper:]]/[[:lower:]]匹配大小写字母写 POSIX 字符类时注意外面要套两层方括号。比如搜索纯数字行grep -E ^[[:digit:]]$ file这套写法在不同语言环境的系统上表现更稳定用[0-9]在某些 locale 下可能会把、这些全角数字也匹配进去而[[:digit:]]更贴近“数字”这个语义。2.3 转义与陷阱什么时候加反斜杠、管道符怎么处理正则里的转义是重灾区。常见的坑可以分为两类。第一类是正则符号本身。比如你想匹配一个普通的点号.比如 IP 地址192.168.1.1直接写grep 192.168.1.1 file会匹配到192x168y1z1这种你根本不想要的东西因为正则里的.是任意字符。正确的写法是转义grep -F 192.168.1.1 file # 或者 grep 192\.168\.1\.1 file-F参数表示固定字符串匹配它把模式里所有正则符号都当成普通字符处理。如果只是找一个明确的字符串-F是最省心的选择性能也是最好的。第二类是 Shell 层面的转义。当你写$、!、\这些符号时Shell 有可能会尝试解释它们。比如grep $PATH app.log由于外层使用了单引号这里的$PATH不会被 Shell 展开grep 收到的是字面量$PATH。如果使用双引号grep $PATH app.logShell 就会把$PATH替我换成环境变量的值然后再传给 grep结果就完全是另一回事了。我在搜索带$或反斜杠的文本时都会坚持用单引号包裹整个模式避免 Shell 这层干扰。另外还有一个经常被忽略的当你在方括号里想匹配]或者-这类符号时注意把-放在开头或结尾、把]放在方括号的第一个位置否则匹配结果会有点意外。2.4 分组、交替与更多量词如果把正则比作一把刀分组和交替就是磨刀时最关键的几步。在扩展正则下圆括号可以把一部分内容打包竖线|表示“或”。比如匹配apple或banana任意一个单词grep -E apple|banana file如果想要更精准一点匹配apple pie或banana piegrep -E (apple|banana) pie file量词这块也特别注意一下。*表示前一个字符出现 0 次或更多次表示 1 次或更多次?表示 0 次或 1 次可选。例如grep -E colou?r file能同时匹配color和colour。这在处理不同拼写、不同命名风格的代码或配置时非常好用。而用定界符更精确时可以用{m,n}表示重复范围grep -E ^[0-9]{4}-[0-9]{2}-[0-9]{2} file这个表达式可以匹配日期开头的行比如2025-06-18 10:30:00。量词默认是贪婪的意思是它会匹配尽可能多的字符。比如对文本a1b2c3用grep -E a.*3会匹配整行。如果想理解得更细可以自己拿-o去看输出差异这是非常直观的练习。如果你在脚本里写正则建议优先用grep -E它比grep默认模式少了一堆转义可读性和维护性都更好。在保持一致性的前提下我会干脆统一用-E处理需要正则的场景不用egrep这种别名。3. 实战场景日志分析、进程过滤与文件搜索3.1 日志分析从一堆日志里快速定位错误日志是 grep 最经典的主战场。真正线上排查问题时日志文件动辄几个 GB你不可能拿编辑器打开看这时候 grep 就是第一把刀。假设有这样一个 Java 应用的日志我需要找出所有出现NullPointerException的行grep -n NullPointerException app.log加上-n让我能看到行号后面如果用sed -n 120,130p app.log或者找同事一起看就能直接定位上下文。但如果我只想看看到底有多少条-c会更直接grep -c NullPointerException app.log注意-c统计的是“匹配的行数”。如果同一行里出现两次异常也只算 1 次。想统计“出现的总次数”要配合-o再数行数grep -o NullPointerException app.log | wc -l这里-o把每个匹配的内容单独输出一行再用wc -l数行数得到的就是真正出现的次数。我通常在排查告警时用这条命令来评估问题的严重范围。如果日志里时间范围跨度很大而我只关心某个时段的报错可以这样过滤grep -E ^2025-06-18 10: app.log | grep -E ERROR|Exception第一层 grep 先圈定时间前缀第二层再筛错。这里第二层用了ERROR|Exception交替模式可以一次性匹配多种关键字。生产环境的日志格式如果比较统一更高效的方案是用 grep 把“包含关键字的行号和上下文”一起带出来然后直接进入下一工作环节。这也是我接下来要说的上下文控制。3.2 ps -ef | grep tomcat 这类进程过滤的完整解读搜索热词里有个特别典型的组合ps -ef | grep tomcat。这个用法在排查进程是否存在、进程 PID 是多少时极为常见。ps -ef | grep tomcat这个命令的流程是ps -ef列出所有正在运行的进程的完整信息包括 UID、PID、PPID、CPU、内存、启动命令等然后通过管道交给 grep过滤出和tomcat相关的行。比如输出可能是root 1234 1 0 Jun18 ? 00:10:23 /usr/bin/java -jar /opt/tomcat9/bin/bootstrap.jar这里1234就是 tomcat 进程的 PID。但很多初学者不知道这条命令还有一个经典坑grep 会把正在执行的那条grep命令本身也匹配进去。因为ps -ef的输出里包含grep tomcat这一行它同样含有字符串tomcat所以结果里会莫名多出一行root 5678 4567 0 00:00:00 grep tomcat解决办法是再加一层过滤ps -ef | grep tomcat | grep -v grep还有一种比较高端的写法利用正则里字符类的技巧让 grep 的模式本身匹配不到那个 grep 命令行ps -ef | grep [t]omcat中括号里的[t]能匹配字符t所以模式[t]omcat可以匹配到真正的tomcat进程。但正在执行的grep命令行字符串是grep [t]omcat它中间包含空格和方括号不满足[t]omcat这个连续模式于是 grep 自己就不会被匹配进来。这招在脚本里很优雅也常出现在 Linux 面试题里。其实在现代 Linux 上更推荐的检查进程方法是pgreppgrep -f tomcatpgrep本身就是用来按进程名或完整命令行找 PID 的不需要再处理过滤自身的烦恼。不过如果你只是想快速确认进程在不在ps -ef | grep xxx依然是最通用、最直观的组合因为它能直接看到进程的启动时间和完整参数。3.3 目录递归搜索与排除文件除了过滤单个日志文件grep 也常用于在源码目录、配置目录里跨多个文件找内容。最基本的是grep -r TODO src/这会递归搜索src/目录下的所有文件找出包含 TODO 的行并且输出时会把文件名也带上src/main/java/com/example/UserService.java:32: // TODO: 这里需要处理用户名为空的情况但如果文件太多目录里又有日志、二进制文件你会被大量无关内容淹没。有两个参数能帮上大忙。第一排除某些文件或目录grep -r --include*.java --exclude-dirtarget TODO src/这样 grep 只会在.java文件里找并且直接跳过target/目录。构建目录、.git、node_modules这些基本都是搜索噪音排除掉以后速度提升特别明显。第二只列出包含关键字的文件不打印具体行内容grep -r -l TODO src/-l模式下每个文件只输出一次文件名。配合xargs可以做批量处理grep -rl TODO src/ | xargs sed -i s/TODO/FIXME/g这条命令先把所有包含TODO的文件列出来然后用xargs把这些文件名传给sed在文件内部把TODO替换成FIXME。这种组合在实际重构大项目时相当常用。3.4 结合 awk、sed、xargs 的处理链grep 负责“找出需要的行”但这只是数据处理的第一步。很多时候你找到行了还得把字段切出来、或者做替换这就需要 awk、sed、xargs 来接力。一个典型的链路从日志中找出所有500状态码的请求并提取出请求路径。grep HTTP/1.1 500 access.log | awk {print $7}awk默认按空格切分$7对应第 7 个字段在很多 nginx 默认日志格式下正好是请求路径。这样你就拿到了所有 500 请求的路径列表。如果再进一步统计哪个接口 500 得最多grep HTTP/1.1 500 access.log | awk {print $7} | sort | uniq -c | sort -rnsort | uniq -c做分组计数再用sort -rn按次数从大到小排排最前面的就是最值得优先排查的接口。再比如把上一小节里的-l和xargs联动还可以批量查看文件行数grep -rl ERROR logs/ | xargs wc -l这里 xargs 把 grep 输出的文件名作为参数传给wc -l一次性统计这些文件的行数。如果文件名中有空格记得给 grep 加上-Z、给 xargs 加上-0用 null 字符安全分隔。我在处理带空格的文件名时吃过亏最开始不知道-0结果文件被截成两段去执行报错之后查出来是这个原因。4. 高级技巧与性能备忘4.1 固定字符串匹配与 fgrep 的价值固定字符串匹配在最开始提过-F它是一个很容易被低估的选项。当你确定搜索内容就是普通字符串、没有任何正则需求时-F的正确率、性能和心智负担都是最优的。举个例子搜索字符串a.bgrep -F a.b file如果不用-F你得这样写grep a\.b file-F多出来的优势不只是省的几次转义。当模式列表很长时-F还支持从文件里读取多个模式grep -F -f error_keywords.txt app.log把常见的错误关键词每行一个放进文件然后一次性匹配。这套用法在批量清洗、批量告警时非常实用。fgrep是固定字符串模式下的老式别名但现代 Linux 上grep -F就是它的实现没必要再单独记一个命令。4.2 上下文控制-A、-B、-C出现异常时只看报错那一行往往不够周围几行才是线索。-A n显示匹配行之后的 n 行-B n显示匹配行之前的 n 行-C n同时显示前后各 n 行比如grep -A 5 Exception app.log对我来说-C 3用得最多。排查调用链超时时看到timeout关键字后前面几行可能是业务参数后面几行可能是对方服务的响应内容有上下文才能把事情还原。上下文除了给终端看还能直接落盘grep -C 3 ERROR app.log error_context.txt这个文件可以直接发给同事或者作为排查附件放进工单里。一个细节当多个匹配行离得很近时-A、-B输出的上下文可能会重叠grep 会在重叠处插入一个非内容分隔行通常是一行--。处理输出时留意一下这个别把它当成业务数据。4.3 性能几个 GB 的日志怎样让 grep 更快日志文件巨大时速度就是生命。有几个原则可以记下来。第一优先用grep -F而不是正则。固定字符串匹配走的路径更短性能好很多。除非必须正则否则别用。第二用-m提前止损。如果只是确认是否存在某条关键日志grep -m 1 OutOfMemoryError app.log只要找到第一处就结束彻底省掉后续扫描时间。第三排除明显没用的文件。递归搜索时把.git、node_modules、target、log这类目录都排除掉。数据量少一个量级速度自然上一个量级。第四如果经常对相同的大文件做多种匹配可以先用一条 grep 把候选行缩小到临时文件再在这个临时文件里二次过滤。这样总耗时往往比多次全量扫大文件低得多。grep -E 2025-06-18 huge.log ./tmp_slice.log grep -c ERROR tmp_slice.log第五如果你确认文件内容都是文本用grep -a可以直接把二进制文件当文本来处理反过来如果只想查文本文件在递归搜索时显式限制--include可以避免二进制文件带来的误报和性能损耗。有个需要在多台服务器大日志上反复排查的场景我会先grep -F配合-m把可能命中的文件找出来再进一步分析。这一套下来比直接在原始日志上反复跑正则搜索要快很多。4.4 与 ripgrep 等现代工具的对比使用这里说点题外话。虽然 grep 是 POSIX 世界里最通用的工具但如果你在一个代码仓库里做高频搜索我强烈建议你试试ripgrep命令名rg。ripgrep 的匹配引擎基于 Rust 实现天然支持多线程而且默认会忽略.gitignore里标记的文件搜索体验非常顺滑。简单对标一下rg TODO src/ grep -r TODO src/在小仓库里两者差距不大但在大型仓库里ripgrep 的速度优势非常显著。但要注意ripgrep 不是 grep 的完全平替它的默认正则语义更接近 PCREPerl Compatible Regular Expressions而且很多老机器的系统环境里没预装。远程紧急排查时你没法保证目标机器上有 rg但一定有 grep。所以我的习惯是日常开发机用 rg 干活线上排查和脚本书写一律用 grep保证最大兼容性。5. 常见问题与排查技巧实录5.1 匹配中文和特殊编码内容有人搜中文日志时发现什么都搜不到。最常见的原因是文件编码问题。日志文件可能是 UTF-8但也可能是 GBK、GB18030或者直接带着 BOM 头。简单测一下grep 错误 app.log如果终端显示乱码或者没有结果先看看文件编码file app.log输出里会直接告诉你charsetutf-8还是其他字符集。确认是 GBK 的话可以先用iconv转码再搜iconv -f GBK -t UTF-8 app.log | grep 错误但要注意这样输出到终端的行是转换后的内容行号对应的是转换后的位置。如果原始日志行数和转换后一致那问题不大如果存在无法转换的字节行号可能偏移。生产环境我更常用的做法是用grep先把疑似关键字可能的十六进制形式匹配出来。中文 UTF-8 编码在正则里写起来比较麻烦如果只是临时查用iconv转码或先看file结果更省事。5.2 特殊字符导致的无结果或误匹配这类问题占了我们日常踩坑的一半。比如搜索文本里带了(、[、{、*、.、$这些字符正则模式下它们都有特殊含义结果要么不匹配要么匹配范围远超预期。一个典型的例子是搜索表达式java.lang.NullPointerExceptiongrep -F java.lang.NullPointerException app.log如果你没用-F.就会当作任意字符你能匹配到类似javaXlangXNullPointerException这样的垃圾内容。虽然大多数情况下结果也能用但终归不严谨。遇到类似场景直接-F是效率最高、最不容易出错的方案。还有一种情况模式里面带了双引号而你又在终端里用双引号把模式包起来了比如grep error: wrong config fileShell 会把字符串切得稀碎最终 grep 收到的模式根本不是你以为的那个。我的原则是单引号包模式真的需要单引号本身时用双引号包整个模式再做一层转义。总之宁可多打几个反斜杠不要追求“能跑就行”。5.3 为什么 grep 找不到内容却不报错遇到搜索结果为空很多新手第一反应是“命令是不是坏了”。其实 grep 没匹配到内容时默认就是不输出任何东西的也不会有提示。这和它的设计哲学一致安静地输入安静地输出只靠退出码告诉你结果。关键排查思路如下检查模式本身。先搜一个必然存在的字符串比如空行grep -E ^$ file确认文件确实被读取、内容可见。检查文件路径。确认你搜的文件确实是你要看的那一个尤其是有软链接、相对路径时。检查文件是否为空或权限不足。ls -lh file看看大小确认用户对该文件有读权限。检查换行符。Windows 的\r\n会让$匹配出现问题。可以用file或cat -A file查看行尾是否有^M$有的话要么转换格式要么在正则里处理\r。还有一个高速排查法直接同时查看匹配行数和你预期的行数grep -c something file如果返回 0 而文件明显包含这个单词那基本就是编码、转义或者大小写的问题。5.4 面试官常问的 grep 知识点结合搜索热词里的linux面试题我总结几个 grep 相关的高频考点你可以自查一下如何使用 grep 统计匹配行数答-c但注意它统计行而不是次数。如何找出文件中不包含某关键字的行答grep -v xx file。如何忽略大小写搜索答grep -i。如何同时匹配多个模式答grep -E a|b file或用多个-e参数grep -e a -e b file。如何递归搜索目录并只输出文件名答grep -rl xx dir/。如何显示匹配的上下文答-A、-B、-C。如何只输出匹配部分而不是整行答-o。退出码分别代表什么匹配到 0未匹配 1出错 2。这些点虽然基础但真正能答全、能现场演示的人并不多。面试官通常用它们来判断候选人是不是真的自己敲过命令而不只是看过文档。5.5 我踩过的三个 grep 坑最后分享几个我比较有印象的坑希望你能绕开。第一个坑是忘记转义管道符号。在正则里|是“或”的意思但如果只用基础正则没加-E或者写脚本时没注意grep error|warn file其实匹配的是含有普通字符error|warn的行而不是error或warn。解决办法就是习惯性加-E。第二个坑是在检查大量日志时没考虑 grep 自身命令被过滤出来。当时脚本里写ps aux | grep java结果脚本莫名把运行命令的 grep 行也统计进去了。后来统一用pgrep和[j]ava技巧绕开了这类问题。第三个坑是在处理文件名带空格或换行符的文件时没配合-Z/-0用xargs。文件名被拆成两段后批量删除脚本直接跑飞了。从那以后我就记住了一句话凡是文件名可能含特殊字符xargs 必须配-0。结语grep 看起来简单但真正用好它靠的是对正则表达式的理解、对输出格式的把控以及对 Shell 层行为和文件编码的敏感。这篇文章里覆盖的命令和思路我在排查线上问题时反复用过大部分内容都能直接抄作业。我个人在自动化脚本里几乎离不开 grep尤其是搭配退出码和-q使用比数行数再去 if 判断要干净不少。也建议你从现在开始每天刻意用几个 grep 选项去处理日常工作比如用-o提取字段、用-C看上下文、用-r跨文件搜索。用不了两周你对它的掌握就会上一个量级。最后一个实用体会如果发现自己在一个长命令里反复用 grep 过滤好几层先停下来想想能不能用一条正则写完。过度使用管道链不仅慢也容易让排查思路变得混乱。grep 应该让事情更简单而不是更绕。