2026/10/26 5:43:44

grep、awk、sed 文本处理三剑客实战指南:从会用到用透

grep、awk、sed 文本处理三剑客实战指南:从会用到用透 文本处理三剑客的实战拆解grep、awk、sed 到底该怎么用很多人第一次接触命令行文本处理都是从一条grep开始的。查日志、筛配置、找报错一条命令下去屏幕上刷刷刷滚出结果那种感觉确实很爽。但真到了要批量改文件、做字段统计、按条件重组数据的时候光靠grep就不够用了于是开始翻awk和sed的手册结果被一堆$1、NR、s///g搞得头晕。我见过不少同行这三个命令用了好几年实际只会grep 关键字 文件和sed -i s/旧/新/g稍微复杂一点的需求就退回写脚本效率白白丢掉一大截。这篇内容就是想把grep、awk、sed这三个命令从“会用”拉到“用透”的层面。我不打算照着手册逐条翻译参数而是按真实工作里最常见的场景来拆怎么精准筛选、怎么按字段做统计、怎么安全地批量替换。每个命令都会讲清楚它解决什么问题、核心机制是什么、参数为什么这么选再配上可以直接复制运行的实例。不管你是刚接触命令行的新手还是已经能写点 shell 但总觉得不够利索的老手应该都能从里面抠出几招能马上用上的东西。1. 先把三个命令的分工掰清楚在动手之前有个认知必须先建立起来grep、awk、sed不是三个可以互相替代的工具它们各自负责文本处理流水线上的不同环节。搞混分工是很多人用起来别扭的根源。1.1 三者的定位差异找、改、算用一句话概括grep负责找sed负责改awk负责算。grep的本质是一个行过滤器。它逐行读入文本判断每一行是否匹配给定模式匹配就输出不匹配就丢弃。它不关心行里面的结构只关心“这一行整体符不符合条件”。所以它最擅长的事情是从一大堆内容里捞出包含某个特征的行。sed的本质是一个流编辑器。它也是逐行处理但它不只是判断而是可以对匹配到的内容做替换、删除、插入、追加等编辑动作。关键在于“流”字——它不需要把整个文件读进内存再改而是一边读一边改一边输出所以处理大文件时内存占用很稳。awk的本质是一个字段级处理语言。它把每一行按分隔符切成若干字段你可以按字段做条件判断、数值计算、格式化输出还支持变量、数组、循环、函数。严格来说它已经是一门小型编程语言了只是语法紧凑到可以写成一行。理解了这个分工很多选择就自然清楚了只是筛行用grep要改内容用sed要按列统计或做复杂条件处理用awk。当然实际工作中三者经常串起来用管道一接各干各的活。1.2 为什么很多人只学会了 grep这里插一句观察。大部分人卡在grep阶段不是因为awk和sed难而是因为没有遇到非用不可的场景。日常的“找一下”需求grep确实够了。真正逼着人往上走的往往是这几类任务日志里按状态码统计次数、配置文件批量改某个字段、从结构化文本里抽某几列重排。这些任务用grep硬做会很别扭用awk或sed就是几秒钟的事。所以下面的内容我会尽量从“什么场景下必须用它”切入而不是干讲语法。语法查手册就有场景判断才是经验。2. grep不只是“搜关键字”grep看起来最简单但它的参数组合能玩出的花样比想象中多。很多人只用过grep 关键词 文件其实它的正则能力、上下文控制、文件级筛选都相当能打。2.1 基础匹配与正则的取舍最基本的用法不用多说grep error app.log这会在app.log里找出所有包含error的行。但这里有个容易被忽略的点默认情况下grep用的是基本正则表达式BRE某些元字符需要转义才能生效。比如你想匹配“以 error 开头”的行写grep ^error没问题但想用、?、|这些扩展元字符就得加-E切到扩展正则ERE或者老老实实加反斜杠。我的建议是只要用到稍微复杂一点的正则直接上-E。可读性好不容易因为转义漏写而匹配错。对比一下# BRE 写法竖线要转义 grep error\|warning app.log # ERE 写法清爽 grep -E error|warning app.log两种都能跑但后者明显更好维护。团队协作时别人读你的命令不用在脑子里做转义换算。还有一个高频需求是忽略大小写加-i即可。日志里Error、ERROR、error混着出现是常态不加-i很容易漏。grep -iE error|fail|exception app.log这条命令在实际排查问题时出场率极高一次把三类异常关键词都捞出来。2.2 上下文参数-A、-B、-C 的实战价值只输出匹配行很多时候信息不够。比如日志里报了一行NullPointerException你光看这一行根本不知道前面发生了什么。这时候上下文参数就派上用场了-A n输出匹配行之后的 n 行After-B n输出匹配行之前的 n 行Before-C n输出匹配行前后各n 行Contextgrep -B 3 -A 5 NullPointerException app.log这条命令会把异常行前面 3 行、后面 5 行一起打出来通常足够看清调用栈的开头部分。我个人的习惯是排查异常时用-B 5 -A 10因为异常信息往往在抛出点之后才展开前面几行则是触发条件。提示-A、-B、-C输出的上下文行之间会用--分隔多个匹配块不会糊在一起这个细节在结果很长时很有用。2.3 递归搜索与文件类型过滤在项目目录里找某个函数在哪定义是日常高频操作。-r递归搜索配合--include限定文件类型能避免把二进制文件、日志、依赖目录全扫一遍grep -rn --include*.py def process_data ./src这里-n是显示行号定位问题时必不可少。--include支持通配符也可以写多个。反过来还有--exclude和--exclude-dir用来排除node_modules、.git这类不该搜的目录grep -rn --exclude-dir{.git,node_modules,dist} TODO .不加排除的话在大项目里搜一次可能要等十几秒加上之后通常一秒内出结果。这个差别在反复搜索时非常明显。2.4 反向匹配与计数-v 和 -c 的组合拳-v是反向匹配输出不包含模式的行。这个参数在过滤噪音时特别好用。比如看配置文件时想跳过所有注释行和空行grep -vE ^\s*#|^\s*$ config.conf^\s*#匹配以可选空白开头后跟#的行^\s*$匹配空行-v把它们都排除掉剩下的就是有效配置。-c是只输出匹配的行数不输出内容。它和-v组合能快速统计有效行数grep -vcE ^\s*#|^\s*$ config.conf再进一步-o配合sort | uniq -c可以做词频统计。-o的意思是只输出匹配到的部分而不是整行。比如统计日志里各状态码出现次数grep -oE status[0-9]{3} access.log | sort | uniq -c | sort -rn这条管道先抽出所有statusxxx片段排序后去重计数再按次数倒序排列。虽然严格说统计活是awk更擅长但grep -o这条路在临时排查时写起来更快。2.5 grep 的边界什么时候该换工具grep的短板很明确它不理解字段结构。一旦需求变成“第二列大于 100 的行”或者“按第三列分组求和”grep就无能为力了硬做只能靠一堆正则拼凑既脆弱又难读。这种时候别硬扛直接换awk。同样需要修改内容时grep只能看不能改得交给sed。3. sed流式编辑的稳与险sed最出名的用法是替换但它能做的远不止替换。删除行、插入行、按行号操作、多点编辑都是它的常规能力。不过sed也是三个命令里最容易出事的一个尤其是-i原地修改用不好会把文件改坏。3.1 替换命令 s 的完整语法拆解替换的基本形式是s/模式/替换内容/标志。很多人只知道s/旧/新/g其实标志位和分隔符都有讲究。先说分隔符。默认用/但如果模式或替换内容里本身含/比如处理路径就得转义很烦。这时候可以换分隔符sed允许用任意字符常见的是#或|# 处理路径时用 # 当分隔符避免转义 sed s#/old/path#/new/path#g config.txt再说标志位标志含义使用场景g全局替换一行内所有匹配都换一行可能出现多次目标时无只替换每行第一个匹配明确只想改第一处时p打印匹配行常配-n只想看被替换的行i忽略大小写GNU sed 支持大小写混杂时数字 n只替换第 n 个匹配精确定位某一处g是最容易漏的。不加g时sed只替换每行的第一处匹配如果一行里有多个目标后面的会被漏掉。这个坑我见过太多次——有人改完配置发现只改了一半排查半天才发现是漏了g。3.2 原地修改 -i 的正确姿势与备份策略-i是原地修改直接把结果写回文件。方便是真方便危险也是真危险——改错了没有撤销。所以我的习惯是永远带备份后缀sed -i.bak s/old_value/new_value/g config.conf这样原文件会被保存成config.conf.bak改坏了随时能恢复。等确认没问题再删备份。多花一秒钟省掉可能的半小时。还有一个跨平台差异要注意某些系统的sed -i要求后面必须跟一个参数哪怕是空字符串写法是sed -i s/.../.../而 GNU 版本则直接sed -i s/.../.../。如果你写的脚本要在不同环境跑这一点会导致命令直接报错。稳妥的做法是先确认目标环境的sed版本或者在脚本里做兼容判断。注意-i修改前务必确认模式匹配范围。一个写得太宽的正则可能匹配到意料之外的行改完才发现问题就晚了。建议先不加-i跑一遍看输出确认无误再加。3.3 按行号与模式删除、插入、追加sed的删除命令是d可以按行号删也可以按模式删# 删除第 5 行 sed 5d file.txt # 删除第 3 到第 8 行 sed 3,8d file.txt # 删除所有空行 sed /^\s*$/d file.txt # 删除包含 DEBUG 的行 sed /DEBUG/d file.txt插入用i在匹配行前插入追加用a在匹配行后追加# 在每个 [section] 前插入一行注释 sed /^\[.*\]$/i # ---- section start ---- config.ini # 在文件末尾追加一行 sed $a # end of file config.ini这里$表示最后一行。i和a后面跟的内容如果含特殊字符建议用单引号包住整个脚本避免 shell 提前解释。3.4 多点编辑与脚本化-e 和 -f当需要一次做多个编辑动作时用-e串联sed -e s/foo/bar/g -e s/baz/qux/g -e /^#/d file.txt多个-e按顺序执行前一个的结果会作为后一个的输入。如果编辑逻辑很长写成脚本文件更清晰用-f引用# 把编辑规则写进 rules.sed # s/foo/bar/g # s/baz/qux/g # /^#/d sed -f rules.sed file.txt脚本化还有个好处规则可以版本管理团队共享比每次手敲一长串命令可靠得多。3.5 sed 处理大文件时的内存表现前面提过sed是流式处理逐行读逐行输出所以即使文件有几个 G内存占用也基本恒定。这一点和“先读进内存再处理”的方式有本质区别。实际测试中处理一个几百万行的日志文件sed的内存占用通常稳定在几 MB 级别速度也很快。但要注意-i原地修改时sed实际上是写一个临时文件再替换原文件所以磁盘上需要额外空间容纳新文件。如果磁盘快满了-i可能中途失败。这种时候可以先输出到新文件确认后再手动替换。4. awk字段级处理的真正威力如果说grep和sed是“行”级别的工具那awk就是“字段”级别的。它把每行拆成列让你能按列做判断、计算、格式化这是它区别于前两者的核心能力。很多人觉得awk语法怪其实它的结构非常规整模式 { 动作 }模式决定哪些行参与处理动作决定对这些行做什么。4.1 内置变量$0、$1、NR、NF、FS、OFSawk的内置变量是理解它的钥匙先把这几个吃透大部分用法就通了变量含义典型用途$0整行内容输出原始行$1~$n第 n 个字段按列取值NR当前行号跨文件累计加行号、按行筛选NF当前行的字段数取最后一列、判断列数FS输入字段分隔符指定按什么切列OFS输出字段分隔符控制输出格式举个最直观的例子打印文件的第二列和第四列awk {print $2, $4} data.txt默认按空白空格或制表符切分。如果数据是逗号分隔的 CSV就要指定FSawk -F, {print $1, $3} data.csv-F是FS的命令行快捷写法。也可以用-F \t处理制表符分隔的文件。NF有个经典技巧取每行最后一列。因为字段数不固定不能写死$5但$NF永远指向最后一个字段awk {print $NF} data.txt倒数第二列则是$(NF-1)。这个写法在处理列数不定的日志时特别顺手。4.2 模式匹配与条件过滤awk的模式部分可以省略表示所有行也可以是条件表达式。比如只处理第三列大于 100 的行awk -F, $3 100 {print $1, $3} data.csv注意这里$3 100是数值比较awk会自动把字段转成数字。如果要字符串比较用或~正则匹配# 第五列包含 error 的行 awk -F, $5 ~ /error/ {print $0} data.csv # 第五列不包含 error 的行 awk -F, $5 !~ /error/ {print $0} data.csv~是正则匹配!~是反向。这个能力让awk在筛选时比grep更精准因为可以限定“只在某一列里找”而不是整行找。比如日志里error可能出现在消息正文里但你只想找状态列是 error 的行用awk就能精确锁定。4.3 统计求和、计数、求平均的写法awk做统计是它的看家本领。求和awk -F, {sum $3} END {print sum} data.csv计数awk END {print NR} data.txtNR在处理到END时就是总行数所以这条命令等价于wc -l但awk还能顺便做别的。求平均awk -F, {sum $3; count} END {print sum/count} data.csv按分组统计是更常见的需求。比如按第一列分组对第三列求和awk -F, {sum[$1] $3} END {for (k in sum) print k, sum[k]} data.csv这里sum是一个关联数组键是分组名值是累加和。END块里遍历数组输出。这个模式几乎可以套用到所有“按某列分组做聚合”的场景改一下累加逻辑就行。再进一步按分组求平均并保留两位小数awk -F, {sum[$1] $3; cnt[$1]} END {for (k in sum) printf %s %.2f\n, k, sum[k]/cnt[k]} data.csvprintf的格式控制和 C 语言一致%.2f保留两位小数。输出对齐、格式统一时用它比print更可控。4.4 BEGIN 与 END 块的使用时机awk的三个块BEGIN、主体、END。BEGIN在读取任何行之前执行一次适合初始化变量、打印表头END在所有行处理完后执行一次适合输出汇总结果。awk -F, BEGIN {print name,total} {sum[$1] $3} END {for (k in sum) print k , sum[k]} data.csv这条命令先打印表头再输出分组汇总结果直接就是一份可读的报表。BEGIN里还可以设置FS、OFS比命令行参数更灵活awk BEGIN {FS,; OFS\t} {print $1, $3} data.csv这样输入按逗号切输出用制表符分隔适合后续粘贴到表格软件里。4.5 awk 与 grep、sed 的管道协作三者串起来用才是完整形态。举个实际场景从日志里筛出错误行提取时间戳和错误码再按错误码统计。grep -E ERROR app.log \ | awk {print $1, $5} \ | awk {count[$2]} END {for (c in count) print c, count[c]} \ | sort -k2 -rn第一步grep筛出错误行第二步awk抽时间戳和错误码两列第三步按错误码计数最后排序。每一步职责单一组合起来却完成了相当复杂的分析。这种“小工具串管道”的思路比写一个大而全的脚本更容易调试——哪一步结果不对单独跑那一步就能定位。5. 三个命令的选型判断与组合套路学到这里语法层面基本齐了但实际用的时候还是会犹豫“这个需求该用哪个”。这一节把常见的判断逻辑和组合模式整理出来相当于一份速查。5.1 一张表看懂该用谁需求首选理由找包含某关键词的行grep行过滤是它的本职找某列满足条件的行awk能定位到字段比整行匹配精准批量替换文本sed流式替换大文件也稳按列统计求和/平均awk字段级计算是它的强项删除/插入特定行sed行级编辑直接支持多条件组合筛选awk支持逻辑运算和正则递归搜目录grep -r递归和文件过滤现成格式化输出报表awkprintf控制格式判断的核心就一句话看需求落在“行”还是“字段”上。行级筛选和编辑交给grep/sed字段级计算和条件交给awk。5.2 常见组合模式筛选-转换-统计最经典的三段式管道grep筛、sed清洗、awk统计。grep 2024- app.log \ | sed s/\[//g; s/\]//g \ | awk {print $2, $6} \ | awk {sum[$1] $2} END {for (k in sum) print k, sum[k]}这个模式的好处是每一步都能单独验证。先跑grep看筛出来的行对不对再加sed看清洗结果最后加awk看统计。出问题时不用猜是哪一步的锅。另一个常见组合是awk生成sed脚本做条件替换。比如只替换满足某条件的行awk -F, $3 100 {print s/^ $1 /REPLACED/} data.csv fix.sed sed -f fix.sed target.txt先用awk根据数据生成替换规则再交给sed执行。这种“数据驱动编辑”的思路在批量处理时很有用规则不是写死的而是从数据里算出来的。5.3 性能对比什么时候别硬用管道管道虽好但不是所有场景都适合。三个命令串起来意味着数据要经过多次读写如果数据量特别大比如上千万行管道开销会累积。这种时候可以考虑用单个awk完成全部逻辑因为awk本身就能做筛选、转换、统计awk -F, $3 100 {sum[$1] $3} END {for (k in sum) print k, sum[k]} data.csv一条awk顶替了grep | sed | awk三段少了两轮数据传递。当然可读性会下降一些所以我的原则是数据量小、逻辑清晰时用管道数据量大、性能敏感时合并到单个awk。这个取舍没有绝对标准跑一下time对比最直接。6. 踩过的坑与实战心得语法和套路讲完最后这部分是我在实际使用中攒下来的经验有些是踩坑换来的有些是反复用之后总结的顺手写法。这些内容手册里不会写但真正影响效率。6.1 正则转义与特殊字符的连环坑grep、sed、awk三者的正则方言并不完全一致这是最容易出错的地方。grep默认 BREsed默认也是 BREawk则更接近 ERE。同一个正则在不同命令里可能表现不同。举个具体的在 BRE 里是普通字符要匹配“一个或多个”得写\在 ERE 里才是量词。所以grep a匹配的是字面的a而grep -E a匹配一个或多个a。这个差异如果不注意匹配结果会完全不对。另一个高频坑是sed替换内容里的特殊字符。替换内容中表示“整个匹配”\1表示第一个捕获组。如果你想替换成字面的得写成\。比如把AB替换成AB本身直接写s/AB/AB/会因为被解释而得到奇怪结果正确写法是s/AB/A\B/。提示拿不准正则行为时先用echo 测试字符串 | grep/sed/awk ...小范围验证确认匹配范围再上真实文件。这个习惯能挡掉大部分误操作。6.2 大文件处理时的内存与速度权衡处理大文件时三个命令的表现差异明显。grep和sed都是流式的内存占用稳定awk如果用了关联数组做分组统计内存会随分组数增长。分组数少比如按状态码分组就几十个键没问题但如果按用户 ID 分组、用户有几百万数组就会吃掉大量内存。这种时候的应对办法是要么先sort再顺序处理牺牲时间换内存要么分批处理。我遇到过一次按请求 ID 统计的场景直接awk数组把内存撑爆了后来改成先sort再逐段处理才稳住。所以看到“按高基数字段分组”的需求心里要有个预警。速度方面grep通常最快因为它只做匹配不做别的sed次之awk因为功能多单行处理开销略大但胜在一次能做完所有事综合下来未必慢。具体场景跑time最靠谱别凭感觉。6.3 先预览再落盘我的操作习惯这条是我最想强调的任何带-i或重定向到原文件的命令执行前先不加修改地跑一遍看输出。# 第一步预览确认匹配范围 sed s/old/new/g config.conf | grep -n new # 第二步确认无误再原地改且带备份 sed -i.bak s/old/new/g config.conf多花这十几秒能避免绝大多数“改错了文件”的事故。尤其是正则写得比较宽的时候预览能直接暴露“匹配到了不该匹配的行”这个问题。我见过有人一个s/.*/.../下去把整个文件改成一行的就是因为没预览。6.4 把常用组合固化成小脚本反复用的命令组合值得固化成脚本或别名。比如我常用的“统计日志里各错误码次数”#!/bin/bash # errstat.sh - 统计日志中各错误码出现次数 LOG_FILE${1:?请传入日志文件路径} grep -E ERROR|FATAL $LOG_FILE \ | awk {print $5} \ | sort | uniq -c | sort -rn存成errstat.sh加执行权限以后./errstat.sh app.log一条命令出结果。比每次手敲管道快得多也不容易敲错。类似的还有“清理配置文件注释行”“按列重排 CSV”等用顺手了就固化下来日积月累能省下大量重复劳动。我个人在实际操作中的体会是这三个命令的价值不在于语法多复杂而在于把判断和操作拆得足够细。grep只管找sed只管改awk只管算每个工具做一件事并做好再用管道串起来。真正熟练之后面对一个文本处理需求脑子里第一反应不是“写个脚本”而是“这条管道该怎么接”。这个思维转变比记住多少参数都重要。