三剑客这三个词,在shell文本处理圈子里被念叨了二十多年,却一点不过时。我最早是被日志分析逼上路的:服务一挂,几十万字的老日志翻到手软,grep半天捞不到关键行,更别说把错误码按次数排个名次。后来才琢磨明白,grep只管“找行”,sed管“改行”,awk管“拆列统计”,三把刀各有各的脾气。这篇就把我从实际项目中攒下来的用法、避坑经历和能直接抄走的组合命令,一次讲清楚。适合刚接触Linux命令行的人,也适合那些天天写脚本但始终没搞懂正则分组、awk数组怎么用的老手。
1. 先搞清三把刀的职责边界,再动手写命令
1.1 它们不是一个时代的产物,却各管一片
grep、sed、awk被并称为文本处理三剑客,不是因为长相相似,而是因为在“逐行处理文本”这个场景里,它们的职责刚好互补,谁也不能完全替代谁。
grep是最早出现的,它的核心能力只有一个:按规则匹配行,然后输出这些行。它不会改内容,也不关心你的文本分几列,只关心“这一行到底有没有命中规则”。所以当你只想确认日志里有没有某个关键字、找出哪些配置文件引用了某个路径时,grep是最快的一把刀。
sed是流编辑器,全称Stream Editor。它的核心能力是按行修改:把A替换成B,删除第几行到第几行,或者把某个范围内的行打印出来。它不像vi那样有交互界面,所有修改通过命令一次性告诉它,它像流水线一样把文件从头读到尾,逐行处理完直接输出。批量改端口、批量去掉配置里的注释,都是它的主场。
awk其实不是典型的编辑工具,它更像一门小型数据语言。它擅长把每一行按分隔符拆成多个字段,然后对字段做判断、运算、统计、格式化输出。你想算出日志里某个接口的平均响应时间,grep和sed都很别扭,awk几行命令就出来了。
一句话总结:找行用grep,改行用sed,拆列算数用awk。三者的共同点是都支持正则表达式,而且都遵循“逐行处理”的哲学,这决定了它们能天然地在管道里协同工作。
1.2 什么时候用哪把刀:一张最小决策表
我见过不少新手一上来就背几百个选项,结果真到用的时候全忘了。我自己更习惯先想场景,再决定工具,选项是靠场景带出来的。
| 场景 | 首选工具 | 典型命令雏形 |
|---|---|---|
| 只想知道哪些行命中关键字 | grep | grep "error" app.log |
| 想统计某个词出现了多少次 | grep + uniq | grep -o "code" x.log |
| 想把配置里的8080改成9090 | sed | sed 's/8080/9090/g' app.properties |
| 想删掉文件里所有注释行和空行 | sed | sed '/^#/d;/^$/d' conf.ini |
| 想按分割列统计聚合数据 | awk | awk '{a[$1]++} END{for(k in a) print k,a[k]}' x.log |
| 想找出响应时间超过1秒的请求并求和 | awk | awk '$NF>1000{s+=$NF;n++} END{print s,n}' x.log |
这张表不需要背,它反映的是一个判断链路:输入是“整行”,输出也是“整行”,那就grep;输入是“整行”,输出却要“改动后的行”,那就sed;输入需要“拆开看字段”,输出是“字段加工后的结果”,那就awk。
我之前在项目中帮同事优化过一个慢脚本,原来他用grep从文件里抠出数字,再用cut拆列,再用循环求和,整个脚本30多行,跑一次还要好几秒。换成awk之后,一行命令就出结果。这不是炫技,而是工具选对了,复杂度自然就降下来了。
2. grep:最快的行匹配器,很多人的用法其实很粗糙
2.1 常用选项:-E、-F、-o、-v、-c 的组合规律
grep的命令格式是grep [选项] 模式 [文件],不带文件名时它读取标准输入。日常最值得花时间练熟的选项就六个:
-E:启用扩展正则表达式。默认的BRE格式里,+、?、|、{、}这些符号都要转义才能用,写起来特别容易踩坑。加了-E之后,直接写+、|、{m,n}就行,跟直觉相符。-F:把模式当作固定字符串,不做正则解释。搜IP、搜URL、搜带点号的配置项时,用这个选项能避免转义,而且速度快一个量级。-o:只输出命中部分,不输出整行。配合sort、uniq可以做精准统计。-v:反向匹配,输出没命中的行。-c:统计命中行数,不输出行内容。-n:输出行号,排查问题定位的时候几乎必用。
一个常见误区是把grep -c当成“出现次数”。它统计的是命中的行数。同一行里出现了10次目标词,grep -c也只会算1次。如果想知道真正出现多少次,得靠grep -o pattern file | wc -l。
2.2 正则里的“行”思维
grep正则的匹配单位是一整行。它的匹配逻辑是:从当前行的任意位置开始找子串,只要有一个位置能匹配上,这一行就算命中。所以grep "abc"并不是判断这一行"等于abc",而是"包含abc"。
很多初学者写grep "192.168.1.1",结果把192.168.111.1也搜出来了,原因就在点号。正则里点号代表任意一个字符,必须写成192\.168\.1\.1才是字面量。更保险的做法是加-F,直接把整串当固定文本来搜。
关于常用符号,我按照从低到高的优先级列几个:
^匹配行首,$匹配行尾。注意$在不同上下文里含义不同,在vim替换里它也有特殊含义,但在grep里就代表行尾。[0-9]、[a-zA-Z]表示字符集合,取反写[^0-9]。\b表示单词边界。搜\btest\b不会匹配contest或testable。(和)是分组,配合|可以做二选一,比如grep -E "error|failed"。
我从实际日志过滤中练出来的一个经验:先想清楚目标特征的最短唯一模式,不要一上来就写一个超长正则。比如要过滤内网IP段,写成^10\.和^192\.168\.各来一条,结合-E和|,比在一个正则里堆三层嵌套分组好维护得多。
2.3 用 -o 从日志中提取并统计错误码
我经常需要统计一份大日志里各种HTTP错误码的数量,做法是先-o抠出错误码片段,再排序去重计数。比如日志里有形如"error_code":"10023"的行,可以这样:
grep -oE '"error_code":"[0-9]+"' app.log \ | sort \ | uniq -c \ | sort -rn这一步的中间产物是若干条"error_code":"10023",sort排序后相同错误码会相邻,uniq -c再压缩并计数,最后的sort -rn按次数降序排列。整个过程没有awk参与,但已经解决了“错误码排行”这个需求。
如果要再进一步,只关心数量最多的前三名,就在末尾加| head -n 3。头部的head不会影响前面的数据流,因为管道是逐行流动的,head取够3行后,会终止读取,整个命令链也会随之结束。
2.4 多文件搜索和空文件名风险
当要搜索整个项目目录时,grep -r很常用,但有两个细节值得注意。第一,-r默认不会进入符号链接目录,如果想跟随,得用-R。第二,搜索结果出现在终端时,加了-H会显示文件名。
把grep当成“行筛选器”丢进管道时,注意一个原则:能用grep过滤的内容,就不要先cat整个文件再传给grep。grep pattern file和cat file | grep pattern结果一样,但前者少开一个进程,对超大文件还有IO上的优势。这个习惯对后面三剑客协作很重要。
在多个文件中找出包含特定词的文件清单,经常配合xargs做批量处理:
grep -rlZ "timeout" config/ \ | xargs -0 sed -i.bak 's/timeout=30/timeout=60/g'这里-Z表示输出以\0结尾,配合xargs -0能正确处理文件名里的空格。如果不加这个,万一某个配置文件路径里带空格,命令就会把文件名拆成两半,轻则操作目标错误,重则删错东西。
3. sed:流式编辑器的核心三板斧,替换、删除、取行
3.1 地址定址:sed命令的门槛和钥匙
sed的语法可以拆成sed 地址 动作。地址决定“对哪些行操作”,动作决定“对命中的行干什么”。没有写地址时,默认对所有行操作。
地址有三种写法:
- 行号:
5代表第5行,1,5代表第1到5行,1~2代表第1行开始每隔2行,也就是奇数行。 $:最后一行。1,$代表全文。- 正则:
/pattern/代表所有匹配该正则的行。
动作常见的有:p打印、d删除、s替换、a追加、i插入、c整行替换。这里最大的坑在于,sed默认会输出所有输入行,而p会把命中的行再打印一遍,结果就是行内容出现两次。所以单独用p时,几乎必须加-n参数,表示“关闭默认输出,只输出我指定要的内容”。
举一个项目里很常见的场景:查看某个时间段附近上下文,grep有-A和-B选项能做,但手工指定范围时,sed的地址组合更灵活:
sed -n '/2025-03-01T10:23:45/,+30p' service.log这条命令会从匹配到2025-03-01T10:23:45的下一行开始,连续打印30行。如果我想从该行往前看10行,sed做不到实时回溯,但可以换成grep -B 10 -A 30 'pattern'。工具之间本来就是补位关系,不必强行用一把刀解决所有问题。
3.2 替换命令的细节:定界符、全局标志、反向引用
替换是sed用得最频繁的动作,写法是sed 's/原模式/替换内容/'。默认情况下,s只替换每一行中第一个命中位置,要处理一行的所有命中,得在末尾加g:
sed 's/8080/9090/g' app.properties当替换内容里有反斜杠或路径符号时,建议换定界符,)之类不常用,最稳妥的是把/换成#或|:
sed 's#/opt/app#/data/app#g' config.path反向引用是另一个高频知识点。模式里的括号分组,在替换时可以用\1、\2引用。注意:使用-E时,分组符号直接写(),引用仍然写\1。我一开始总以为扩展正则模式下引用也要改成别的写法,实际上完全不用。
假设有一批日期格式是2025-03-01,想统一成2025/03/01:
sed -E 's#([0-9]{4})-([0-9]{2})-([0-9]{2})#\1/\2/\3#g' dates.txt这种写法把脏活交给正则,sed只做搬运。批量重命名文件名时,也经常用这个思路:先用ls或find把文件名列出来,再通过sed生成新的文件名,再用mv执行。
3.3 删除空行和注释行,配置文件清洗最常见
sed删除动作的大概逻辑同替换一样,地址选在哪,d就删除哪行。清理配置文件时,我常用这一串:
sed '/^#/d; /^$/d' nginx.conf分号表示在同一行里连续执行多个sed子命令,等价于sed -e '/^#/d' -e '/^$/d' nginx.conf。这条命令会去掉所有以#开头的行和所有空行。要注意,前后有空格或制表符的空行并不算严格意义的^$,更严谨的写法是/^[[:space:]]*$/d。
我想强调一点:sed只是在输出流里把这些行删掉了,并没有修改原文件。很多人误以为sed操作完文件就变了,实际上如果想落盘,要么重定向到新文件,要么用-i。给新手最常见的建议永远是:先用不带-i的命令看输出,确认结果没问题,再加-i。
3.4 原地修改 -i 的跨平台陷阱
-i选项能直接改文件。但GNU sed和BSD sed在参数上有差异,这是跨平台脚本最容易翻车的地方。
- Linux系统自带的是GNU sed,支持
sed -i 's/a/b/g' file。 - macOS自带的BSD sed强制要求
-i后面跟一个备份后缀,必须写成sed -i.bak 's/a/b/g' file,否则会报错。 - 想让两边都能跑,通用写法是
sed -i.bak,这样Linux上会生成file.bak备份,BSD上也正常。如果实在不想留备份文件,GNU下用-i即可,BSD下可以写-i '',但引号里的空格细节很容易出错。
我之前给某个跨平台部署脚本踩过坑,在macOS开发机上执行好好的sed -i命令,一上Linux生产环境反而报错,查了半天发现是两种sed对-i的参数处理方式完全不同。后来统一改成sed -i.bak,两边不再打架。
-i之前一定要先做无参预览,这是血的教训。我曾经在生产环境上一个配置批量修改命令少了转义,一整批配置文件里的内容全乱了,最后从备份里费了好大劲才恢复。现在我的习惯是三步走:先grep -n看目标内容,再sed不带-i做输出预览,最后确认无误才加-i。
3.5 不只是替换:sed 的拼接顺序和二进制安全提醒
多数人对sed的印象停留在替换,其实它还常用来做“提前拼接”和“范围提取”。比如给每一行自动加前缀后缀:
sed 's/.*/<item>&<\/item>/' source.list这里的&代表整个命中的字符串,替换后原行会被包进一对<item>标签。写脚本生成XML或HTML片段时非常好用。注意,&在替换字符串里是特殊符号,如果替换内容里真的想写&,得写成\&。
还有一个我遇到过很多次的坑:sed处理二进制文件时会输出类似“binary file matches”或乱码。用grep搜二进制文件时也类似。解决方案是加-a选项,把二进制文件当文本处理,或者干脆用strings命令把可打印字符提取出来再处理,后者在分析二进制包时更靠谱。
4. awk:按列思考和统计的利器
4.1 程序结构:行循环、字段拆分、BEGIN和END
awk对很多初学者来说最难,因为它不是单一的查找替换,而是一个“模式-动作”结构。它的执行模型可以这样理解:
- 自动逐行读取输入。
- 每一行,awk先按分隔符把行切成若干字段,
$0代表整行,$1代表第一个字段,依次类推。 - 如果写了“条件”部分(也叫模式),就只在条件为真时执行“动作”部分。
BEGIN块在所有行处理前执行,通常用来设置变量、定义分隔符、打印表头。END块在所有行处理完后执行,通常用来输出统计结果。
默认的字段分隔符是任意连续空白,包括空格和制表符。想自定义分隔符,用-F或者BEGIN里的FS变量。处理CSV时,-F','是最常见写法。我经常用-F'[,\t]+'来处理既带逗号又带制表符的导出文件,正则作为字段分隔符的灵活度是cut完全比不上的。
举一个秒懂的入门例子。假设score.txt内容如下,三列分别是姓名、科目、分数:
张三 语文 89 李四 数学 93 张三 数学 88统计张三的总分:
awk '{if($1=="张三"){s+=$3;n++}} END{printf "%s total=%d, avg=%.1f\n", "张三", s, s/n}' score.txt这里s和n是自定义变量。awk很多用法就是把Excel里干的那套统计逻辑搬到命令行里,字段即列,循环则是隐式的。
4.2 统计求和、平均值、最大值,一行搞定
awk统计能力在生产里最常用的场景之一就是分析接口响应时间。假如latency.log只有一列数字:
awk '{sum+=$1; if($1>max) max=$1; n++} END{printf "sum=%.2f max=%.2f avg=%.2f\n", sum, max, sum/n}' latency.log如果日志是完整的access log,响应时间在最后一列,那就用$NF,这是awk内置变量,代表最后一个字段。这个技巧可以避免去数字段却死活数错的尴尬。
我曾经分析过一个批量任务日志,任务耗时都在每行最末尾,格式还不太统一,有毫秒有秒。如果用sed光是单位换算就够呛,awk却能做到按字段先拆分再统一单位。在END块里做汇总输出,整个过程不会引入临时文件。
4.3 按列过滤:grep管不到的地方
grep只能按整行正则过滤,想“某列大于多少才输出”,awk才是正解。最典型的场景是看磁盘占用:
df -h | awk '$1=="data" && $5>80 {print $1, $5}'再比如从一份tsv文件里抽出第3列长度大于10的行:
awk -F'\t' 'length($3)>10 {print $0}' data.tsvawk内置函数length直接提供列内字符计数,不用在正则里痛苦地写{11,}。
我说过一句话,“awk天生适合干在多个字段之间做逻辑判断的活”,这句话的底气就在于它的条件部分可以是多个字段的组合。比如:
awk '$1 ~ /^10\./ && $2=="open" {count++} END{print count}' netstat.log这里的~是正则匹配运算符,表示“左边字段是否匹配右边正则”。反向不匹配用!~。这个语义比grep整行匹配更精细,也更贴近业务含义。
4.4 数组、去重和统计排行
awk里的数组本质上是一个关联数组,下标可以是字符串,这让“按某个维度分组统计”变得极其简单。比如统计一个访问日志里各来源IP的请求次数:
awk '{cnt[$1]++} END{for(ip in cnt) print ip, cnt[ip]}' access.log | sort -k2 -nr我刚开始接触awk数组时最纠结的是它没有END块里的数组顺序概念,for(ip in cnt)的遍历顺序是哈希顺序,不是写入顺序,也不是排序顺序。所以如果要top N,再丢给sort处理最省事。
去重也是awk的强项。保留第一次出现的行,逻辑核心是靠数组记录“是否见过这个字段”:
awk '!seen[$0]++' duplicat.txt这行代码的精妙处在于seen[$0]++这个表达式是通过“先取值再自增”的顺序判断的。第一次遇到某行时,seen[$0]是0,!0为真,所以打印;第二次遇到时,seen[$0]是1,!1为假,直接跳过。这个技巧简洁但不好读,我建议写进脚本里时注释清楚,免得两星期后自己都看不懂。
4.5 printf与字段格式化
awk里常用的printf格式符和C语言的几乎一样:%s字符串、%d整数、%.2f保留两位小数、%-10s左对齐宽度为10的字符串。它在生成报告的时候堪称神器。比如把统计结果以表格形式输出:
awk '{cnt[$1]++} END{printf "%-15s %8s\n", "IP", "Count"; for(ip in cnt) printf "%-15s %8d\n", ip, cnt[ip]}' access.log这比直接print好读得多,也更方便粘到邮件或文档里。awk还有substr、split、index等字符串函数,能处理不少轻量级文本格式转换,但如果转换逻辑太复杂,我建议直接用Python一把梭,awk的功能边界也就到这里了。
5. 组合拳:管道协作,就是一段迷你ETL
5.1 管道本质:上一命令的stdout是下一命令的stdin
三剑客真正威力是它们在管道里协作。管道不产生临时文件,数据像水流一样从左到右流动。这带来一个好处和一个陷阱。
好处是处理大文件时不会占用额外磁盘,也不会在目录里留下一堆中间产物。陷阱是管道中某一步出错时,后面拿到的就是残缺数据,排错时容易头晕。我的经验是:排错时把管道拆开执行,逐步加段。比如grep ... | sort | uniq | awk ...如果结果不对,先跑grep段看输出,再加sort看中间结果,最后再加awk。
命令行里还有一个隐形选择:无脑使用cat file | ...其实是冗余的。文件路径可以直接放在grep/sed/awk后面。写脚本时尽量避免无意义的管道,因为每多一个进程,大文件处理时间就多一分。
5.2 场景一:按时间段统计接口状态码
假设有一个access log,每一行格式是IP 时间 状态码 耗时,要统计某天15点到16点之间各状态码的数量:
grep "15:" access.log \ | awk '{count[$3]++} END{for(code in count) print code, count[code]}' \ | sort -k2 -nr如果时间格式是25/Mar/2025:15:23:45,那么第一个字段就不是我们要的时间字段,需要调整字段位置,比如用$4。写命令前先head -n 2看一眼日志格式,永远是最稳的做法。
这种两步走模式在日志分析里极其常用:grep负责行为级粗筛,awk负责字段级聚合。两步之间靠管道交接,逻辑清晰,性能也够用。
5.3 场景二:批量修改配置并保留备份
我在部署某些应用时经常需要把一批配置文件里的监听地址从旧IP换成新IP。习惯做法是三步:
- 先
grep -rl "192.168.1.10" conf/找到所有涉及到的文件。 - 再对这些文件逐一执行替换预览,确认替换结果。
- 最后原地替换并保留备份。
一条管道的写法是:
grep -rlZ "192.168.1.10" conf/ | xargs -0 sed -i.bak 's/192.168.1.10/192.168.1.20/g'有人会问,这时候为什么不用for循环?其实也可以:
for f in $(grep -rl "192.168.1.10" conf/); do sed -i.bak 's/192.168.1.10/192.168.1.20/g' "$f"; done但$(...)遇到空格路径会出问题,而grep -Z | xargs -0更健壮。命令行里能用管道表达的操作,我优先用管道,循环越多,出错可能性越大。
5.4 场景三:从报表里提取关键词生成CSV
sed和awk还能携手完成一次迷你ETL。假设有一个企业应用的日志文件,里面的行有不同级别,格式是INFO 模块名 耗时,现在要提取所有ERROR级且耗时超过1000的行,并且按模块名,耗时输出成CSV:
sed -n '/^ERROR/p' app.log \ | awk '$NF > 1000 {print $2 "," $NF}' \ | sort -t',' -k2 -nr这里的-t','指定sort的分隔符,后面-k2 -nr表示按第二列数字倒序。CSV如果字段里有逗号,这种简单做法会出问题,但处理内部日志足够用了。更严谨的CSV转义会复杂很多,不是三剑客的主要战场。
5.5 组合中的常见坑:grep -v 之后还要再处理空行
有一次我从日志里反向过滤掉INFO行,用了grep -v "INFO",结果输出的文件里还有大量空行,后续awk统计时因为字段数量不对,计算结果全都错了。后来我在管道的下一步加了sed '/^$/d',问题立刻解决。
这类“数据依旧不干净”的坑非常多。每次组合工具时,都应该在心里过一遍:当前管道输出的数据格式,能安全地被下一个工具解析吗?空行、行尾空格、字段分隔符突变,是最常见的三类污染源。
grep -v "INFO" app.log | sed '/^[[:space:]]*$/d' | awk '{...}'6. 避坑实录与性能调优:三剑客踩过的常见大坑
6.1 三套正则标准:BRE、ERE、PCRE,别混着用
grep默认使用基本正则(BRE),在这个模式里,+、?、|、()这些字符都被当作普通字符,想表达正则含义必须加反斜杠,写出来是\+、\?、\|、\(...\)。很多新手的第一个坑就出现在这里,明明在别的工具里能用的正则,粘到grep里全部失效。
我强烈的建议是:日常使用统一加-E。grep -E是扩展正则(ERE),+、?、|直接生效,不需要转义。而grep -P使用PCRE,兼容更多语言风格,但性能通常略逊于-E,而且在某些最小化系统里-P可能没编译进去。生产环境脚本尽量别依赖-P。
sed同样分BRE和ERE:默认BRE,-E切换ERE。awk则默认就是类似ERE的语法,所以+、?、|直接可用。写脚本前先确定工具,再选择正则写法,可以省掉一大半调试时间。
6.2 贪婪匹配:正则看起来没毛病,结果却不对
正则从左到右匹配,默认是贪婪的:它会在保证整体匹配成功的前提下,尽可能匹配更长的内容。经典案例是提取引号里的内容:
如果你写".*"去匹配"a" and "b",它会一次匹配到"a" and "b",中间引号也被吞掉了。原因就是.*太贪婪,它一直滑到最后一个引号才罢休。解决办法不是去学正则里的“懒匹配”选项,而是用更精确的字符集合:
grep -oE '"[^"]*"' data.txt[^"]*表示“任意多个不是双引号的字符”,它从第一个引号开始,到第一个不是引号后又遇到引号的地方就停下来。这个模式比.*?更直观,也更稳定。我对所有文本提取类需求的建议都是同一句话:在能明确排除分隔符时,永远优先用排除型字符组,而不是依赖贪婪/懒匹配。
6.3 不同系统、不同版本之间的行为差异
Mac依然默认保留BSD sed和一套老版本bash,Linux则是GNU系,二者在-i、正则支持、行尾处理上都有差异。awk也同样:有的系统自带的是mawk,有的则是gawk,它们在浮点精度、strtonum函数、中文字符支持上略有差别。
如果脚本要跨平台跑,我的建议是:
- sed统一使用
-i.bak,避免-i参数差异。 - awk脚本里避免使用gawk特有的扩展函数(如
strtonum、gensub),尽量只用POSIX标准函数。 - 正则尽量别用
\d、\w这类Perl风格符号,改用[0-9]、[[:alnum:]]。 - 涉及日期、locale时,考虑在脚本开头设置
LC_ALL=C,避免中文本地化环境干扰字符匹配。
我曾在一个自动化巡检脚本里用awk '/[[:alpha:]]/{print}'过滤只含字母的行,在Linux上正常,在某个老Solaris机器上却把中文混进来不少。后来统一LC_ALL=C,行为才稳定。
6.4 大文件性能:管道进程、固定字符串与循环
三剑客处理几十GB的日志时,容易产生性能问题。性能瓶颈通常不是工具本身,而是错误用法。
第一,能用grep -F固定字符串就不要写成正则。-F会跳过正则编译和匹配,速度提升两到三倍。比如你只需要找GET /api/order/list这一串,用grep -F 'GET /api/order/list'。
第二,awk里尽量不要一行匹配写一个单独的正则,把多次匹配合并到一次条件判断里。比如:
awk '/error|failed|timeout/ {count[$1]++}'只运行一次正则,比写三个if要快。
第三,避免在awk中频繁调用system()或管道。比如awk '{while(("grep " $1 " file")|getline) ...}'这种写法,每行都会启动一个子进程,处理一万行就会启动一万次命令,性能极其糟糕。本质上说明需求设计得不对,应该先把数据整理好再一次性处理。
第四,能用管道、单遍扫描完成的,就不要引入for循环加临时文件。三剑客的优势在于全程流式,一旦落盘,IO开销马上变大。
6.5 中文和字符集:\w、\b的坑
在UTF-8环境下,\w在很多实现里会匹配汉字,因为汉字属于Unicode的“单词字符”。这会导致你认为只在过滤英文字母,结果把中文也带了出来。同理,\b词边界对CJK文本的判定也不可靠。
解决方案是尽量不要依赖这种Perl风格字符类,改用POSIX字符类:[[:alpha:]]匹配字母,[[:digit:]]匹配数字,它们受locale影响,但加LC_ALL=C之后行为更可预期。在纯ASCII日志场景下,我一般会在脚本开头加LC_ALL=C,彻底避免本地化干扰。
6.6 文件路径含空格:xargs 的经典翻车现场
这其实是三剑客管道协作时最容易踩的坑。find、grep输出文件名时,如果文件名含空格,xargs默认按空白分割输入,会把一个文件名拆成多个参数。我在某次批量改配置文件时,就因为一个带空格的文件名,导致sed把命令参数拼错,直接报错退出,排错花了快两个小时。
正解是在源头就使用空字符分隔。grep -rlZ、find -print0、xargs -0是一套完整方案,-Z和-0就是让数据流以\0而不是空白分割。只要路径可能带空格,一律用这套组合。
还有一点,如果用管道把grep输出喂给while循环,建议写成while IFS= read -r line; do ...; done < <(grep ...)或grep ... | while ...。前者能保持循环变量不回传,但细节多,篇幅原因,这里就不展开了。
最后再分享一个值得长期用的小技巧
这三把刀用久了,最值得养成的习惯不是背命令,而是“先看数据结构,再挑工具”。任何文本到了你手上,先问三个问题:我要匹配的是“整行”还是“列”?输出要不要修改原行?要不要做分组统计?答案基本就决定了grep、sed、awk各自登场还是组合上阵。
我自己经常把一段三剑客命令存成shell脚本,再用一个参数控制输入文件和输出列。比如一个统计脚本日后可以复用,只要改模式串和awk字段位置就行。这样的脚本比直接用Python重写要轻量,启动快,在服务器上没装Python环境时也能跑。
如果你刚开始练习,我建议每天拿一份真实日志做十分钟训练:先用grep -n找到目标段,再用sed批量修改,最后用awk做一次统计。坚持两个月,你的命令行效率和排错直觉都会明显变化。三剑客从来不是最潮的技术,却是运维和开发手里最可靠的一把老锯,磨一磨,比很多新工具都好使。