文本处理三剑客 grep、sed、awk 实战指南:从日志分析到脚本优化
发布时间:2026/10/20 2:12:01 锦皓数字建站

1. 写在前面为什么文本处理离不开这三个命令如果你在日常工作中经常跟日志、配置文件、数据报表打交道肯定绕不开 grep、sed、awk 这三个名字。它们被统称为“文本处理三剑客”在 shell 脚本里是最基础也最实用的组合。grep 负责筛选sed 负责修改awk 负责统计和格式化三者各自擅长一件事配合管道用起来几乎能解决九成以上的文本处理需求。这篇文章不是从零讲命令手册而是基于我实际使用这些命令的经验把每个工具的定位、常用参数、实战场景和容易踩的坑一次说清楚。无论你是刚接触脚本的新人还是已经写过不少脚本的开发者我相信都能从这里面找到对自己有用的细节。我看过很多人写了很久脚本遇到文本处理还是只会用 grep要么就写一堆笨重的循环其实换个工具十几行就能解决。我最早开始系统用这三个命令是在处理一批几百 MB 的访问日志时。那时候我还在用编辑器打开文件用鼠标滚轮翻找关键字效率低得可怕。后来学会了 grep 过滤、sed 着替换、awk 做统计才真正体会到命令行处理文本的威力。这篇文章就以我自己的使用路径为主线把这三把刀一个个拆开讲清楚。2. grep最快的筛选器没有之一2.1 grep 的核心思路行匹配不是字符串匹配很多初学者学 grep 时有个误区以为 grep 是“寻找某个字符串”其实它的工作单位是“行”。grep 会逐行读取输入内容判断这一行是否匹配给定的模式匹配就输出这一行不匹配就跳过。这个机制看起来简单却决定了它的很多行为特点。举个例子你想在文件里找所有包含“error”的行grep 会输出整行内容而不是单独输出“error”这个单词。如果你只想要单词本身得配合其他工具或者使用-o选项。这里的“匹配”默认是子串匹配也就是说“errors”“error404”这些行也会被匹配到。很多人刚开始用的时候容易被这种模糊匹配误导以为 grep 输出的是精确结果其实它只做模式匹配不做语义判断。我在处理日志时经常用grep -w来限定单词边界。-w的意思是只匹配完整的单词比如grep -w error就不会匹配到 “errors”。这个选项在排查问题时非常实用能减少不少误报。2.2 grep 的常用参数与场景grep 的参数很多但真正高频的其实就那么几个。我按使用频率排个序参数作用典型场景-E使用扩展正则表达式需要写 -i忽略大小写匹配 ERROR 和 error 混用的日志-v反向匹配输出不匹配的行过滤掉注释行、空行-w按单词匹配避免匹配到包含关系-o只输出匹配到的部分提取 IP、邮箱、时间戳-n显示行号定位代码或配置问题-c统计匹配行数统计日志中出现次数-A/-B/-C输出匹配行的后/前/上下若干行查看异常上下文这里特别说一下-E。grep 默认使用的是基础正则表达式在基础正则里、?、|这些符号需要转义才能生效很容易把人绕晕。加上-E之后就能直接用、?、|和我们在其他语言里写的正则习惯一致。所以我现在写 grep 命令基本都带-E省去很多转义麻烦。-o是我后来才重视的参数。比如你想从日志中提取所有 IP 地址不关心整行内容就可以用-o配合正则输出。它只会打印匹配的部分每个匹配结果占一行。这个功能在后续组合 awk 做统计时特别有用。2.3 grep 实战日志筛选用法假设你有一个应用日志app.log每行记录一条访问信息。现在需要找出所有状态码为 500 的记录并且要看到每个记录之前的 5 行内容方便排查出错前发生了什么。这个需求用grep -E -B 5 status[:]500就能搞定。-B 5会把匹配行之前的 5 行也打印出来中间会用分隔线提示。如果只想统计某个接口今天被调用了多少次可以用grep -c /api/user/info app.log返回的是匹配的总行数。如果你需要区分大小写或者日志里同时存在 “User” 和 “user”就加上-i。我自己的习惯是先做一次快速统计确认异常量级再决定要不要去翻上下文。反向匹配也经常用到。比如配置文件里有很多注释行你想看有效配置可以用grep -v ^#过滤掉以#开头的行。生产环境中排查问题时我常把 nginx 配置里的注释和空行去掉再看实际生效的指令这时候grep -v -E ^#|^$一条命令就搞定了。grep 还有一个容易被忽略的细节当输入文件特别大时它不会把整个文件读入内存而是流式处理。所以哪怕文件有几十 GBgrep 也能正常工作只是磁盘 IO 会慢一些。这也是它在文本处理三剑客里定位为“第一道过滤器”的原因——先用 grep 快速缩小范围再用 sed 或 awk 做精细处理。3. sed流式编辑器批量替换的利器3.1 sed 的工作原理按行读取、处理、输出sed 的全称是 Stream Editor流式编辑器。它不像 vim 那样需要打开整个文件而是像流水线一样读一行处理一行输出一行然后读下一行。这种模式决定了 sed 天生适合处理大数据文件不会因为文件太大而卡死。很多人对 sed 的印象停留在“替换”但它实际能做的不只替换还包括删除、插入、打印、按行范围处理等。只是日常使用中替换是最高频的需求所以我先把它的核心机制讲清楚。sed 的基本语法是sed 操作指令 文件。操作指令由“地址范围”和“动作”组成。地址范围可以是一个行号、一组行号、一个正则匹配也可以什么都不写表示对所有行执行动作。动作则包括s替换、d删除、p打印、a追加、i插入等。我们看几个最常用的表达方式。sed -n 5p file表示只打印第 5 行-n的作用是关闭默认输出因为 sed 默认会打印所有处理过的行如果不加-n第 5 行会被打印两次。新手经常在这里犯迷糊我来解释一下sed 默认的行为是把每一行都输出到标准输出而p动作会额外输出一次。所以sed 5p file会把第 5 行输出两次。加-n之后默认输出被关闭只有明确写p的地方才会输出。以后你看到sed -n p这种写法就理解为“只打印符合条件的行”。3.2 sed 的增删改查四类操作我把 sed 的核心操作按功能分为四类查、删、改、增。查也就是打印主要用于提取指定行。比如sed -n 20,30p file会打印第 20 到 30 行。这在查看大文件的某一段时比head和tail组合更精确。head -n 30 file | tail -n 11虽然也能做到但不如一条 sed 命令直白。删用d动作。sed /^#/d file会删除所有以#开头的行。这个操作和 grep 的-v效果类似但 sed 的优势在于可以结合行号范围。比如sed 10,20d file删除第 10 到 20 行。需要注意d动作会直接改变输出结果但不会修改原文件除非你用-i写回文件。改最常见的是s替换。格式是sed s/原模式/替换内容/标志。标志常用的是g表示一行内所有匹配都替换没有g的话每行只替换第一个匹配。比如sed s/old/new/g file就是把每行的所有 “old” 变成 “new”。如果只想替换每行第一个去掉g就行。增用a和i动作。a表示在匹配行后面追加内容i表示在匹配行前面插入内容。比如sed /error/a\处理超时请检查 file会在每个包含 error 的行后面新增一行文本。注意a\后面直接跟要追加的内容中间不要放多余空格。3.3 sed 实战配置文件批量修改我举一个真实场景。某公司的应用配置文件nginx.conf里有大量注释行现在需要把所有监听端口从 8080 改成 9090并且只修改前 100 行中的配置避免误伤后面的规则。我先过滤出需要改的行grep -n listen nginx.conf确认端口出现的行号。然后用sed -i 1,100s/8080/9090/g nginx.conf进行修改。-i是直接修改文件生产环境操作前一定要备份我看到过不少人因为漏了备份改坏了配置回退都麻烦。如果我只是想临时查看替换后的效果不加-i就行输出会打印到屏幕原文件不受影响。这个习惯特别好用先不改文件跑一次替换用diff对比观察确认无误后再加-i。比如sed s/8080/9090/g nginx.conf | diff nginx.conf -如果输出为空说明没有差异否则会列出不同的地方。sed 的替换能力还能配合“地址范围”做更复杂的操作。例如sed /upstream/,/}/s/8080/9090/g file表示从匹配到 upstream 的行开始到第一个}行结束在这个区间内做替换。这种范围和脚本逻辑配合起来非常强大。4. awk不是命令是小型编程语言4.1 awk 的数据模型记录、字段、内置变量如果说 grep 和 sed 还是“命令”那么 awk 更像是一门小型编程语言。它的设计核心是“处理结构化文本”也就是那些有固定分隔符、可以按列拆分的文本。比如表格数据、日志记录、/etc/passwd这类文件都是 awk 的主场。awk 默认把一行称为一条“记录”记录内部按“分隔符”分成若干“字段”。默认分隔符是空白字符空格和 Tab。内建变量NF表示当前记录的字段数量$0表示整行$1、$2分别表示第一个、第二个字段。NR表示当前处理的是第几条记录也就是行号。这几个变量一定要背熟。举一个最简单的例子awk {print $1} file会打印每行的第一个字段。如果你把头文件也输出就可以用NR判断awk NR1 {print $1} file跳过第一行。这个写法在处理带表头的数据文件时太常用了。awk 还允许通过-F指定分隔符。比如处理/etc/passwd字段是用冒号分隔的命令是awk -F: {print $1} /etc/passwd。这个文件正好可以用来验证 awk 的字段拆分能力第一列是用户名。4.2 awk 的常用模式BEGIN、END、条件、循环awk 程序的基本结构是“模式 { 动作 }”。没有模式时动作会对每一行执行。模式可以是正则、条件表达式、BEGIN、END 等。BEGIN在 awk 读取任何行之前执行适合做初始化工作比如定义变量、打印表头、设置分隔符。END在所有记录处理完之后执行适合做统计汇总。这两个模式是 awk 报表能力的关键。举个例子统计一个文件的行数可以写成awk END {print NR} file。NR 在最后一条记录处理完后值就是总行数所以用 END 直接输出即可。同理统计某个字段的总和可以用awk {sum $1} END {print sum} file。这就是 awk 比 grep 和 sed 强在的地方它能做复杂的聚合运算。条件匹配也是 awk 的强项。比如awk $2 100 {print $1} file会打印第二列大于 100 的行的第一列。这种基于数值比较的过滤grep 做起来很别扭awk 却非常自然。awk 里还支持if、while、for等流程控制。很多人觉得这已经超出“命令行工具”的范畴了确实如此。awk 本质上就是一个解释器只是它专为文本行处理做了优化。4.3 awk 实战日志统计与报表生成我来完整演示一个用 awk 统计日志的场景。假设访问日志的每一行格式是192.168.1.10 - - [10/Jan/2024:13:00:01 0000] GET /api/v1/users HTTP/1.1 200 532注意默认分隔符是空白字符所以时间戳、引号里的请求行都会被当成字段拆开。但我们只需要 IP、状态码、响应大小这些字段用默认分隔符就够了。需求是统计每个 IP 的访问次数并按次数从高到低排序。用 awk 加 sort 可以一行完成awk {count[$1]} END {for (ip in count) print ip, count[ip]} access.log | sort -k2 -rn这里的count[$1]用 IP 作为数组下标统计每个 IP 出现的次数。for (ip in count)是遍历数组在 END 块里输出结果。管道后面的sort -k2 -rn表示按第二列数字倒序排序这样访问量最大的 IP 会出现在最前面。再比如统计每天请求量。日志里时间戳字段是$4格式是[10/Jan/2024:13:00:01开头是日期。我们可以用字符串截取函数substr($4, 2, 11)取出从第 2 个字符开始的 11 个字符得到10/Jan/2024然后按这个分组计数awk {daysubstr($4,2,11); count[day]} END {for (d in count) print d, count[d]} access.log这就是 awk 报表能力的体现。你甚至可以生成表格形式加上标题和汇总行awk BEGIN {print Day\tCount} {daysubstr($4,2,11); count[day]} END {for (d in count) print d \t count[d]; total0; for (x in count) totalcount[x]; print Total\t total} access.log这种写法虽然比简单的一行命令长但可读性和输出质量都高很多。5. 三剑客协同如何组合使用解决复杂问题5.1 管道接力grep 过滤、sed 修改、awk 统计单个工具的能力总是有限的真正让三剑客强大的是管道组合。管道的概念很简单把前一个命令的标准输出作为下一个命令的标准输入。这样数据就像流水线上的零件一样被不同工序加工。最常见的组合模式是先用 grep 过滤出感兴趣的行再用 sed 对字段做一些替换清洗最后交给 awk 做统计和输出。我举个实际的例子有一个多行日志文件里面混着 DEBUG、INFO、WARN、ERROR 各种级别现在要统计每个 ERROR 消息中出现的接口名次数。先用 grep 把所有 ERROR 行取出grep ERROR app.log然后如果 ERROR 行包含了额外的前缀或时间戳我们只关心接口路径可以用 sed 提取或去掉多余部分。假设接口路径出现在引号中的 URL 里比如/api/user/info而且接口名和状态码之间有一个关键字。先做替换把不需要的部分去掉grep ERROR app.log | sed s/.*path\([^ ]*\).*/\1/这里的 sed 用到了分组捕获把 path 后面直到空格前的部分提取出来。最后用 awk 统计每个路径出现次数grep ERROR app.log | sed s/.*path\([^ ]*\).*/\1/ | awk {count[$0]} END {for (path in count) print path, count[path]}这条组合命令在排查接口异常时非常好用。你不需要打开文件逐行看一条命令就能得到一个高频错误接口的排名。5.2 一个综合案例解析 Nginx 日志我把上面的思路整合成一个完整案例处理一份 Nginx 访问日志。日志样例127.0.0.1 - - [10/Jan/2024:13:00:01 0000] GET /api/v1/order/list?page2 HTTP/1.1 200 1024需求统计所有响应状态码为 4xx 和 5xx 的请求中哪个 URL 被访问的次数最多。第一步用 grep 过滤状态码。状态码在日志中的位置不确定但一般会在引号后的字段。Nginx 默认日志格式下$9是状态码但因为行内包含引号和多个空格直接用默认分隔符会导致字段位置偏移。我通常先用 awk 本身就着条件过滤awk $9 400 {print $7} access.log$7是请求行里的 URL 路径$9是状态码。awk 可以同时做过滤和提取这一步其实不需要 grep。但为了演示三剑客协同我们也可以先用 grep 把包含 4xx 或 5xx 的行过滤出来。不过 grep 对数值范围判断不擅长直接 awk 更合适。现在的目标是统计 URL 出现次数awk $9 400 {count[$7]} END {for (url in count) print url, count[url]} access.log | sort -k2 -rn | head -n 10这条命令一次完成了过滤异常状态码、按 URL 分组计数、排序取前十。如果 URL 中包含查询参数比如/api/v1/order/list?page2可以先用 sed 把?之后的参数去掉只要路径部分awk $9 400 {print $7} access.log | sed s/?.*// | awk {count[$0]} END {for (url in count) print url, count[url]} | sort -k2 -rn | head -n 10这里用两次 awk 虽然有点浪费但逻辑很清晰第一次做过滤第二次做聚合。实际使用中也可以用一个 awk 完成但那样会牺牲可读性。对于排查问题来说可读性比节省一次管道重要得多。6. 常见问题与避坑指南6.1 正则差异BRE vs ERE正则表达式是三剑客的通用基础但不同工具对正则的支持不同这是我踩过最多坑的地方。grep 默认使用基础正则表达式BRE在 BRE 里、?、|、(、)这些元字符都需要转义才能代表特殊含义。比如你要匹配 “apple” 或 “banana”得写成grep apple\|banana这种写法非常容易出错。所以我习惯用grep -E开启扩展正则表达式ERE这样可以直接写apple|banana和大多数编程语言保持一致。sed 默认也是 BRE所以sed s/\(a\|b\)/X/g里面的括号和竖线都要转义。等到用sed -E之后写法就变成sed -E s/(a|b)/X/g顿时清爽很多。awk 则直接用 ERE它对正则的支持更接近 Perl 的风格比如支持\d在某些版本里不一定可用最好用[0-9]代替。如果你在写正则时发现 “为什么我的 不生效”或者 “为什么 | 被当成普通字符”先检查有没有给对应的工具加上-E。这个经验能帮你省下大量排查时间。6.2 编码与特殊字符处理文本处理时最容易忽略的坑是编码问题。如果文件是 UTF-8 编码包含中文而你的终端环境是 GBKgrep 匹配中文时可能失败或者找到的结果显示乱码。处理这类问题最稳的方法是先查看文件编码用file命令确认然后统一用 UTF-8 环境处理。另一个坑是特殊转义字符。比如你想在 sed 里替换路径/etc/nginx/nginx.conf因为/作为分隔符路径里也包含/会产生冲突。解决办法是换成其他分隔符比如#sed s#old/path#new/path#g。这是 sed 非常实用的技巧。同理替换内容中含时需要注意在 sed 替换串里代表“匹配到的整个内容”如果确实需要输出字面量需要用\转义。awk 里处理引号也容易出错。awk 脚本本身通常用单引号包裹如果脚本内容里出现了单引号就得通过转义或者改变外部引号来解决。最安全的方法是避免在 awk 脚本里直接用单引号需要表示字符串时改用双引号。6.3 性能对比与选型建议三剑客虽然都能处理文本但性能差别明显。对于大文件grep 的速度通常最快因为它只做匹配和输出不做复杂计算。sed 次之因为它按行处理但替换操作会涉及字符串修改。awk 最慢但它能做最复杂的事情。我给出的选型建议是只需要筛选行用 grep不要为这个需求写 awk。需要批量替换、删除指定行用 sed。需要按列拆分、聚合统计、条件运算用 awk。实际工作中很多人喜欢一条命令解决所有问题结果写了又长又难懂的 awk 脚本。但如果先用 grep 缩小范围再用 awk 处理少量数据整体性能反而更好。比如你要统计文件中某个关键字的出现次数grep -c keyword file比awk /keyword/ {count} END {print count} file快很多。道理很简单grep 是专用工具针对匹配优化过。此外管道虽然方便但每经过一层管道都会增加进程启动和 IO 开销。处理超大文件时尽量避免无谓的管道链。比如grep foo file | sed s/bar/baz/可以尝试通过 sed 的地址范围一步完成但前提是逻辑允许。性能优化不该影响代码可读性在做判断时找到平衡点。7. 我的经验总结与练习建议最后聊点个人体会。三剑客的学习曲线不算陡但想熟练需要刻意练习。我刚上手时总觉得 grep 就够用了后来遇到一个把表格数据按列汇总的需求才发现 awk 一行能顶一大段脚本。从那之后我开始有意识地在日常操作中练习 sed 和 awk。建议大家准备一个真实的日志文件比如 Nginx 或者自己写的应用日志然后给自己出几个小题目找出访问量前五的 IP统计每个状态码的数量把日志里的 IP 替换成脱敏后的字符串计算所有请求响应时间平均值。这些题目覆盖了三剑客的核心功能做一遍比看十篇教程都有用。做练习时注意先备份文件不要轻易用-i直接修改生产数据。可以先输出到标准输出或者重定向到临时文件检查结果。我见过很多次因为少写一个g标志只替换了每行第一个匹配最后数据一团糟的例子。正确的做法是先用sed s/old/new/g file | head看看效果再决定是否写回。还要养成一个习惯在脚本里写注释说明每个命令的意图。grep、sed、awk 的语法可读性并不高过一段时间你自己可能都看不懂当时写的命令。我后来写这类脚本都会在关键行上加注释配合set -x调试效率高很多。这篇文章讲的内容是我在实践中真正用到过的东西。ps我自己的备忘清单里三件套的命令组合大概占了三分之一篇幅每次用的时候查一遍就会加深一次印象。工具不难难的是把它用到该用的地方希望这篇总结能帮你少走一些弯路。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。