做运维和开发这些年我发现自己处理得最多的其实不是业务代码而是文本日志、配置、接口返回、数据文件。只要你在终端里待得够久迟早会面对“怎么从几百MB日志里把报错拎出来”“怎么把一批配置文件里的IP批量换掉”“怎么把这堆带分隔符的字段做个汇总”这类需求。而 grep、sed、awk 这三个命令就是解决这些问题的三把钥匙合称“文本处理三剑客”。它们在 shell 脚本里的地位就像螺丝刀和扳手在工具箱里的地位看起来基础真用熟了脚本能写得又短又稳省下的时间足够多喝两杯茶。这篇文章是 shell 脚本系列的续篇重点就是把 grep、sed、awk 掰开揉碎讲一遍。我不打算只罗列参数而是结合真实场景说明每个命令解决什么问题、为什么这样写、脚本里有哪些坑最后再串起来用管道组合成完整方案。无论你是刚接触 shell 的新手还是写过不少脚本但总觉得这几个命令“会用但不熟”的朋友都能在这篇里找到可以直接抄走的东西。1. 重新认识这三个命令grep、sed、awk 的核心定位很多人一提到 grep、sed、awk第一反应是“我好像都会点但真到写脚本时一律用 Python”。坦白讲这不丢人我早期也是这样。但后来我意识到很多临时需求根本不需要起一个 Python 文件在终端里一条管道就能解决。关键是要理解三剑客的分工而不是把三个命令混为一谈。1.1 三剑客的核心定位搜索、变换、计算这几个命令的功能边界可以用一句话概括grep 负责“找”它按正则或字符串规则把匹配的行全部挑出来sed 负责“改”它逐行处理文本能做替换、删除、提取、插入核心是“流式编辑”awk 负责“算”它是三个工具里最“重”的能按字段拆分、做条件判断、累计统计甚至支持数组和循环。用一个不太严谨但好记的类比grep 像安检员负责从人堆里把符合条件的对象拦下来sed 像整形医生能对每一条记录做微调、重塑awk 像会计拿到一堆明细后按列归类、加总、出报表。三者各干各的事但又能串起来配合。正因为各司其职所以遇到文本处理需求时第一步不是打开搜索框找工具而是先问自己我是要筛选、修改还是统计这个判断做对了后面什么命令、什么参数都顺理成章。1.2 遇到需求时怎么决定用哪个我总结了一套很粗糙的决策思路实战里足够用了。如果只是想知道文件里有没有某个关键词、有多少行匹配、匹配的是哪些行那直接上 grep。比如检查某配置文件里有没有开启 debug或者统计日志里 500 错误的次数grep 是最快、最低成本的选择。如果需要对文件内容做修改把某个路径全部替换成另一个路径、删除掉所有注释行、只保留第 10 到第 20 行、给批量生成的文件头部插入一行说明那用 sed。它最大的优势是不用打开编辑器能在脚本里对文件“原地”操作。如果数据是表格结构比如日志的每一行都用空格或逗号隔开若干字段需要做求和、计数、去重统计或者要根据某一列的值决定怎么处理另一列那用 awk。它有变量、有算式、有 END 块能把一个简单的统计需求压缩成一行命令。当然实际业务里往往不是单一命令能搞定的。数据要先 grep 过滤出符合条件的行再 sed 做格式清洗最后 awk 汇总。这就是管道加三剑客的组合玩法也是后面第 5 节的重点。先把每个工具单独钻明白组合时才不会乱。2. grep最快的搜索过滤器grep 可能是很多人最早接触的 Linux 命令之一但会敲grep xxx file和真正在脚本里用好 grep是两码事。它最大的优势是“快”而快的背后是它能直接吃文件、能按正则匹配、能只输出需要的部分。2.1 常用参数和它们的实际含义先过一遍我脚本里出现频率最高的几个参数。-E启用扩展正则这样可以用、?、|、()这些符号而不必写一堆反斜杠。绝大多数场景我都会用-E比默认的基础正则好读得多。-v反向匹配输出不满足条件的行常用于排除注释行、排除空行、排除 debug 日志。-c只输出匹配行数。注意它是计数不输出具体内容。-l只输出包含匹配内容的文件名适合在同目录下批量搜索“哪些文件里有这个关键字”。-r递归搜索目录。-o只输出匹配到的部分而不是整行。这个参数很妙能从一行日志里“抠”出 IP、订单号、错误码。-n显示行号排查配置或定位日志时特别好用。-A/-B/-C分别显示匹配行之后、之前、前后的上下文行查日志报错时几乎是必需品。-q安静模式不输出任何内容只靠退出码判断是否有匹配适合在 if 条件里使用。-P启用 Perl 兼容正则支持前瞻断言这些高级用法但要注意不同系统的 grep 版本支持度不一样移植性略差脚本里慎用。举个我日常会写的例子。验证一个配置项是否开启可以写if grep -q ^enable_cachetrue /etc/某app.conf; then echo 缓存已开启 fi这里用-q就是让 grep 不要往终端吐一堆东西只通过退出码告诉脚本“找到了没”。这是 grep 在脚本中最高频的用途之一。还有提取日志里的订单号一行日志里混了很多信息但只需要 ID 的部分grep -oP order_id:\K[0-9] order.log | head -n 10\K是 PCRE 里的一个特殊写法表示“从这里开始才算真正匹配的内容”用起来像是一个可变长度的零宽断言。它能把前面一大段条件吃掉但只输出后面真正想要的值。如果你在别的机器上发现-P不好使也可以用sed或者调整正则来替代不过开发调试阶段用-P确实爽。2.2 实战场景从日志里快速筛出错请求我处理过这样一个需求。某服务突然出现一批异常请求日志文件已经滚了好几个每个都有几百 MB。要定位问题第一步不是打开文件拖滚动条而是先过滤出状态码为 500 的请求。grep -E HTTP/1.1\ 500 access.log | head -n 50有人可能会问为什么不用grep 500 access.log直接搜因为 500 可能出现在 URL、响应体、客户端 IP 等很多地方直接搜会混入大量不相关的行。加了前后边界匹配更精确。确认格式之后再统计量grep -cE HTTP/1.1\ 500 access.log如果想要结合时间范围还可以再加一个条件grep -E ^2025-06-.*HTTP/1.1\ 500 access.log | wc -l这里的关键点是grep 只能按“行”做匹配如果要按时间范围、字段值做精细筛选它稍微有点吃力这时就该请 awk 出场。所以我的经验是grep 做粗过滤、量级过滤awk 做字段级精确统计各管一段。2.3 脚本里用 grep 容易踩的坑grep 看着简单但我在脚本里踩过几次印象深刻的坑。第一个坑是管道退出码。单独执行grep foo file匹配不到时退出码是 1这个很好理解。但如果有管道比如grep foo file | sed s/bar/baz/整条管道的退出码默认是最后一个命令的退出码sed 基本总会成功于是即使 grep 没匹配到内容管道整体依然返回 0。如果脚本里用if grep foo file | sed ...; then判断就会失真。解决办法是在脚本开头设置set -o pipefail让管道中的任何一个命令失败都导致整条管道失败。第二个坑是变量内容里的特殊字符。比如把用户输入的内容作为 grep 的模式时如果变量里包含.、[、*这些正则符号匹配结果会和你预期的完全不同。一种做法是改用grep -F让模式按固定字符串处理或者先对变量做转义。不要想当然地直接把变量塞进正则里。第三个坑是循环里反复 grep 同一个文件。有人写脚本时图省事while read -r line; do echo $line | grep -E error done big.log这写法逻辑没错但性能极差。big.log 有多大这脚本就跑多久。正确写法是直接用 grep 一把梭所有需要的逻辑都交给正则处理grep -E error big.log如果非要在循环里判断也应该先一次性把结果读到变量里再遍历变量而不是在循环里反复读盘。3. sed流式文本编辑器如果你第一次跟 sed 打交道会有一种奇怪的感觉它明明是个“编辑器”但打开的不是交互界面而是“数据流”。sed 逐行读入、逐行处理、逐行输出全程不打开文件。正因如此它天生适合脚本。3.1 寻址、替换和常用命令sed 的基本格式是sed [选项] 命令 文件。最核心的命令是s也就是替换。sed s/foo/bar/ file这里只替换每一行第一次出现的 foo。要全局替换需要加g标志sed s/foo/bar/g file要忽略大小写加i标志sed s/foo/bar/gi file如果想把匹配到的内容做删除用d命令sed /^#/d file.conf想只打印某些行用-n配合psed -n 10,30p file sed -n /^\[server\]/,/^\[client\]/p file.ini后面这种“从匹配某模式的行到匹配另一模式的行”的区间打印特别适合在日志或 ini 配置里抽取段落。它不用知道你具体要知道字符偏移量只要知道两个边界就行。地址也可以精确到行号也可以混用。例如删除所有注释行和空行sed /^#/d; /^$/d config.txt我在脚本里最常用的其实还是批量替换文件。需要“原地”修改时用-ised -i s/192\.168\.1\.100/192.168.1.101/g /etc/某app/*.conf注意点号在正则里是“任意字符”如果不转义可能把192x168这种不在预期的字符串也替换掉。写的时候要格外留意。3.2 实战用 sed 做配置批量迁移记得有一次某个服务要换存储 IP配置文件分散在三四台机器的多个目录下。要是手动改得打开十几个文件肉眼搜索加替换难免漏掉。我用 sed 做的就很快。先预览效果不改文件sed s|old_storage_ip|new_storage_ip|g /path/*.conf | grep -i storage这里我故意把定界符从/换成了|因为 IP 和路径里的斜杠太多了用/作分隔符要写一串反斜杠可读性极差。sed 的s命令定界符可以是任意字符只要紧跟命令后就行。替换路径这类含斜杠的文本选|或#作分界符几乎成了我的习惯。确认输出没问题之后再用-i原地替换。为了稳妥我先带备份后缀sed -i.bak s|old_storage_ip|new_storage_ip|g /path/*.conf这样万一改错了还能从.bak文件恢复避免手滑造成的惨案。改完再检查一遍grep -rn new_storage_ip /path/*.conf整个流程下来前后不到五分钟。如果遇到 sed 替换内容里本身带符号要注意在 sed 里代表“匹配到的原字符串”。比如sed s/abc/123/g会把 abc 替换成 abc123。如果你替换文本里恰好要写字面意义的就得写成\。这个细节我第一次用的时候没想到结果配置被写歪了排查了半天。3.3 写 sed 脚本最容易犯的错误sed 看着短但写复杂时就容易翻车。总结几个我反复念叨的注意点。第一-i的兼容性。sed -i.bak在 GNU sed 和 BSD sed 上的行为有点差异。某些系统上sed -i必须要给后缀有些必须要-i 。在跨平台脚本里我一般用-i.bak这种通用写法同时做好备份既能覆盖大多数场景也顺带保护了原文件。第二变量替换的转义。在脚本里把 shell 变量塞进 sed 表达式是常有的事sed -i s/old_path/$new_path/g file.conf如果$new_path里包含/那整个表达式就乱了。我的经验是优先更换定界符比如sed -i s#old_path#$new_path#g。如果变量里还有#那就要考虑用sed之外的工具比如 awk通过-v传参会更安全。第三sed 不适合处理多行块。虽然它也有N、H这些高级命令可以做多行模式但可读性和维护性都很差。一旦发现自己在 sed 里写出一连串分号加奇怪字母组合的命令停下来想一想是不是该换 awk 了。脚本是给人读的不是写给命令炫技的。4. awk真正的文本计算引擎如果说 grep 和 sed 还算“文本处理”那 awk 已经跟“计算方法”沾边了。它能把输入按字段切开还能做变量、运算、条件、数组。很多人被它的语法劝退但把它当一门微型语言来看反而好理解得多。4.1 核心概念字段与分隔符awk 处理每行时默认用空白字符空格、Tab把行拆成多个字段。第一个字段叫$1第二个叫$2以此类推。$0代表整行。NF表示当前行字段个数所以$NF就是最后一个字段。最简单的用法echo foo bar baz | awk { print $1, $3 }输出foo baz。这里逗号默认会被替换成空格输出如果想自定义输出分隔符可以设置OFSawk -v OFS| { print $1, $3 } file如果输入文件的分隔符不是空白比如 CSV 或者用冒号分隔的/etc/passwd就用-F指定awk -F: { print $1, $3 } /etc/passwd-F也可以传正则。比如从日志里按多种空白符切分awk -F [ ,]。这个在字段格式不稳定的场景下很实用。在脚本里awk 最常见的入门用途就是这么打印字段。但请注意awk 里的$1和 shell 里的环境变量没有关系。想从外部传值进来用-v最干净awk -v num$count { print $1, num } file这个细节我在刚接触时容易搞混总想直接在单引号里用$count结果每次都会得到空值或语法错误。4.2 实战做求和、平均、分组统计awk 最让我喜欢的地方是它能“一行命令做统计”。打印某个文件第 3 列的总和awk { sum $3 } END { print 总和:, sum } data.txt求平均值awk { sum $3; count } END { print 平均:, sum / count } data.txt这里的END块会在所有行处理完之后执行一次。相比之下BEGIN块是在读文件之前执行的常用来打印表头、初始化变量。只统计满足条件的行awk $2 100 { count } END { print count } data.txt也可以直接用条件输出比如打印第 2 列大于 100 的整行内容awk $2 100 data.txt这在 awk 里有默认动作等价于{ print $0 }写起来很简洁。我再举一个稍微综合的例子。一个统计接口耗时的日志里面每行有两列第一列是接口名第二列是耗时毫秒。我想算出每个接口的平均耗时这时 awk 数组就排队上场了awk { total[$1] $2; n[$1] } END { for (api in total) print api, total[api] / n[api] } result.log这里total[$1]是以第一个字段为下标的数组累计第 2 列的值n[$1]累计出现次数。最后在END块里遍历数组逐个求平均值。输出顺序可能不固定但统计结果是准确可用的。如果想排序再加个| sort -k2 -rn就行。这个例子看起来很高级其实核心就是数组加循环awk 已经把大部分逻辑封装得很直接了。4.3 awk 脚本中的条件、循环与调试技巧awk 里的语法贴近 C 语言所以如果你写过一点编程适应它完全没有问题。但有几个点需要注意。变量不需要声明类型直接用即可。比如sum $1时sum 初值是 0数组不存在时初值是空串。正因为如此脚本里写“判断数组里有没有某个 key”时建议用if (key in array)而不是if (array[key] ! )避免空值造成的误判。调试时我最常用的是在 awk 表达式后直接加print看中间结果。比如想确认字段切分是否正确可以先跑一句awk { print NR, NF, $1, $2 } file | headNR是当前已读入的行数NF是当前行字段数。这两货在调试里极为好用。字段切分不对输出会非常明显一看就知道是不是分隔符设置错了。写复杂的 awk 脚本时除了命令行里的一行式也可以把 awk 程序写到单独的脚本文件里然后awk -f script.awk file调用。这样多行逻辑、注释都方便写。很多从命令行 awk 转为复杂统计需求的人最后都会走上-f这条路这是从“临时命令”到“正式脚本”的分水岭。5. 组合使用让三剑客在管道里协同工作单一命令能做的事终究有限真正能解放生产力的是把 grep、sed、awk 通过管道串起来让数据像流水线一样经过每一道工序。这也是“三剑客”称号的真正含义。5.1 一个经典流程过滤到清洗到统计我把最常见的组合方式抽象成三步流程先用 grep 做粗过滤快速缩小范围去掉无关行再用 sed 做文本清洗把不需要的字段、前缀、后缀删掉或者把数据替换成统一格式最后用 awk 做字段统计输出真正想要的结果。举个例子。某服务的日志每一行大致长这样2025-06-01 10:00:12 INFO request_id10293 api/order/create status500 cost_ms230现在要看今天 500 错误的主要集中在哪些接口上以及每个接口出现次数。grep status500 service.log \ | sed s/.*api\/[^ ]*// \ | awk { print $1 } \ | sort | uniq -c | sort -rn我这里稍微偷懒写了个有点粗糙的 sed 提取逻辑实际中会按照日志具体格式仔细写正则。重点是思路grep 把 500 错误的行都选出来sed 把整行裁剪成只保留接口名awk 再做字段拆分最后sort | uniq -c | sort -rn完成统计排序。一条命令下来人工翻日志可能要十分钟脚本跑就是几秒钟。5.2 从访问日志中快速统计高频 IP 的完整流程再举一个更具体的场景也是我经常被问到的分析访问日志看哪些 IP 请求最多而且只看状态码为 403 的请求。grep 403 access.log \ | awk { print $1 } \ | sort | uniq -c | sort -rn | head -n 20这里的关键是 grep 选出了包含 403 状态码的行awk 取出第一列也就是客户端 IP。后面的sort | uniq -c | sort -rn | head是统计高频项目的经典管道段不是三剑客的标配但经常和它们搭配出现。如果需求再细一点比如想统计某个时间段内 403 请求的来源可以继续加条件grep 403 access.log \ | grep ^2025/06/01 \ | awk { print $1 } \ | sort | uniq -c | sort -rn | head -n 10用两条 grep 虽然看着重复但对日志做粗过滤效率依然很高而且思路非常直白后面接手的人也能一眼看懂。遇到这种组合管道我建议在脚本里分步写而不是非要一次写成巨人命令。比如先过滤出结果存进临时文件再处理临时文件。这样做的好处是中间结果能随时检查定位问题更快。不要为了追求“一行流”而牺牲可维护性。5.3 把常用组合封装成脚本函数生产脚本里我不会把一堆组合命令直接散落在各处而是会封装成函数方便复用和维护。比如写一个函数统计某个文件或输入里指定字段的平均值avg_field() { local file$1 local field$2 awk -v f$field { sum $f; n } END { if (n 0) print sum / n } $file }再比如写一个重定向日志过滤函数提取指定时间段的错误日志extract_errors() { local logfile$1 local keyword$2 grep -E 2025/06/01|2025/06/02 $logfile | grep -i $keyword }函数好处在于单个命令的规则可以集中修改不影响业务逻辑。我个人的习惯是凡是超过两步的管道就认真考虑封装凡是超过一次要复用的就直接放进脚本库而不是每次重新敲一遍。6. 常见问题和调试实录用到一定量之后你会发现三剑客遇到的坑其实很有规律。我把这几年碰到的、以及身边同事抱怨过的常见问题整理成一个速查希望你能绕过我走过的弯路。6.1 引号、转义和正则细节先看一组非常容易翻车的例子。变量在双引号和单引号里的解释方式不同。比如keyworderror grep $keyword file.log # 正常变量会被展开 grep $keyword file.log # 不会展开变量只会找字面量 $keyword绝大部分时候我们都希望变量展开所以用双引号。但反过来想在grep正则里使用单引号内的特殊字符更安全因为双引号对$、\、反引号都会处理。所以规则很简单模式里没有变量时用单引号避免意外展开有变量时用双引号但要注意变量里的正则符号。sed 的替换模式也容易踩类似雷。比如想把一段文本里的点号.替换成下划线_直接写echo a.b.c | sed s/./_/g得到的是___而不是a_b_c。因为.在正则里是任意字符。要匹配字面量点号必须转义echo a.b.c | sed s/\./_/gawk 则要注意另一个常见错误在单引号程序里不能直接使用 shell 变量。比如awk { print $1, $limit } file # $limit 在 awk 里可能是空值或字段引用正确做法是用-v传递给 awkawk -v limit$limit { print $1, limit } file6.2 管道退出码与文件备份问题脚本安全是我特别想强调的。默认情况下管道的退出码等于最后一条命令的退出码这一点你已经知道了。如果脚本里包含set -e那么一条本来期望失败的 grep 管道就可能让整个脚本提前退出或者反过来把失败掩盖掉。我的习惯是在正式脚本开头写set -Eeuo pipefail其中pipefail让管道中任意一条命令失败都会导致整体失败配合set -e能让脚本在关键时刻停下来而不是带着错误继续往下跑。至于 sed 的-i请务必先备份。执行sed -i.bak后原文件会生成一个.bak备份。这样哪怕替换规则写错也能一键回滚。有人说这多此一举、占磁盘但等真的把线上配置改坏一次就会明白备份有多重要。6.3 性能避免在大循环里反复调用外部命令再好的命令用错了场景也会卡成狗。最常见的问题就是“在 as while 循环里反复执行 grep”。我见过不少人写这样的脚本统计日志中各种关键字次数while read -r line; do count$(echo $line | grep -c some_pattern) total$((total count)) done huge.log从逻辑上没问题但每一行都会启动一个进程去执行 grep而 grep 本身又需要重新扫描输入内容这样跑起来会非常慢。对百万级行日志这种写法的耗时能达到正确写法的几十倍。正确做法是把统计交给 awk 一次性完成awk /some_pattern/ { count } END { print count } huge.log如果已经没有办法避免循环也至少应该先 grep 过滤一遍得到小规模结果之后再在这个结果上做循环处理。另外一个性能细节grep -F比普通正则搜索快得多。当你的匹配模式是固定字符串、不包含正则符号时尽量加上-F告诉 grep 不做正则解析。这在搜长文本、大文件时提升明显。6.4 我积累的几条实用小经验最后分享几条实操里越来越顺手的小经验。排查问题时别急着把整条管道一次性执行。先单独跑 grep确认过滤出来了多少行再跑 sed看清洗后的中间格式最后跑 awk 验证统计结果。分步调试最小化出错范围。如果管道很长我会把第一道命令的结果写进临时文件再基于临时文件做后续操作这样每步都能随时复查。正则里优先用“锚定”提升准确性。比如^user:比user:更安全status500比500更精确。写模式时先问自己有没有可能误匹配到其他内容多写几个边界条件能省掉后面排查误报的大量时间。还有一个很小的习惯在 aws 脚本里给 awk 加NR调试打印确认行数和字段数。在grep -c统计时确认输出为 0 是“真没有”而不是“命令没跑”。这听起来像废话但紧急状态下最容易忽略的就是这类前提检查。我自己刚接触这三剑客时总觉得“会敲命令”和“写得好脚本”是两回事。用着用着发现真正的分水岭不是记住多少参数而是能不能从需求反推该用哪个工具、怎么组合、怎么防错。希望这篇能帮你把三剑客通过串联起来在下次面对一堆日志和配置文件时能少一点烦躁多一份悠然。 SEO 优化官网定制响应式建站教育培训建站