news 2026/9/14 5:17:38

grep、sed、awk三剑客实战:从日志分析到文本处理的完整方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
grep、sed、awk三剑客实战:从日志分析到文本处理的完整方案

开头想直接扔一段"grep、sed、awk三兄弟"的废话?不行。

我见过太多次这样的场景了:开发同事线上排查问题,日志文件几百兆,他打开编辑器从头翻到尾,小半天过去了还在骂骂咧咧找某个异常栈;或者是报表脚本里的文本清洗逻辑写得又臭又长,跑一次要几分钟,明明用awk一行就能搞定的活,非得写二十行shell循环。说白了,问题就出在没把grep、sed、awk这三个命令当成真正趁手的工具来练。

这篇不是走马观花的命令列表,而是我这些年真刀真枪用下来的实操笔记。grep负责捞数据,sed负责改数据,awk负责算数据——三个命令单一拆开都能干活,组合起来才是文本处理的完整流水线。适合刚接触Linux、被各种管道符绕晕的新手,也适合那些明明用过很久但总觉得差点意思、想系统补一遍短板的老手。看完不需要记住所有参数,但得知道什么时候该用哪个、遇到具体需求该往哪个方向查。

1. 为什么非要死磕这三个命令

先说个反直觉的事:在Linux生态里,处理文本最高效的方式不是写脚本,而是"组合命令行工具"。

你写Python或者Perl处理日志,启动解释器就要几百毫秒,处理大文件可能还要装第三方库。但grep、sed、awk是从Unix诞生起就存在的元老级工具,C语言实现、单进程流式处理,跑几百MB的文件也就是秒级的事。更关键的是,这三个命令严格遵守Unix哲学:每个工具只做一件事,通过管道把它们串起来。

管道这个东西,好多新手理解不到位。比如grep "ERROR" app.log | sed 's/ERROR/错误/g' | awk '{print $1}',这条命令的意思是:grep先筛出包含ERROR的行,sed把ERROR替换成"错误",awk再取每行第一个字段。上一个命令的标准输出,直接变成下一个命令的标准输入,全程数据在内存里流动,不产生中间文件。流的理念贯穿这三个命令的始终,理解了这个,后面所有操作都顺了。

另外,这三个命令在手,几乎能涵盖日常90%的文本处理需求:

  • 搜日志找关键信息:grep搞定;
  • 批量改配置文件:sed搞定;
  • 按列做统计、求和、格式化输出:awk搞定;
  • 再复杂的联动需求:管道串起来搞定。

好多人在图形界面里拖来拖去,或者写一次性脚本,其实时间成本远高于静下心来把这三个命令练熟。磨刀不误砍柴工,说的就是这个。

2. grep:最快速度定位你想要的文本

2.1 别只会grep "关键字"

grep全称是Global Regular Expression Print,全局正则表达式打印。它最基本的用法确实就是grep "关键字" 文件,但实际工作里,需求从来不会这么简单。

最常见的场景是搜日志。应用日志里同一时间可能有几十个线程在输出,你只关心某个交易号的记录:

grep "T20240615A00321" merchant-service.log

这样是能搜出来,但如果日志被轮转过、打包成.gz了怎么办?grep有个很有用的参数--include配合通配符,再结合-r递归,一次性搜索整个目录:

grep -r --include="*.log" --include="*.log.gz" "T20240615A00321" /data/logs/

注意,.gz文件grep没法直接读,这种场景建议用zgrep,它是专门为压缩文件设计的:

zgrep "T20240615A00321" /data/logs/*.log.gz

我知道你大概率还不知道这个命令,它跟grep的参数用法几乎完全一致,但不需要解压就能搜gz包里的内容。记下来,这能让你少掉一半头发。

2.2 正则才是grep的灵魂

grep搜固定字符串只能算入门,配合正则表达式才是它的完全体。日常使用频率最高的是这几个:

参数功能常用场景
-E使用扩展正则,相当于egrep用`
-o只输出匹配到的部分从日志里提取IP、订单号
-v反向匹配,排除含有指定模式的行过滤掉DEBUG日志
-c统计匹配行数(不是匹配次数)统计错误次数
-n显示匹配行所在行号告诉同事bug在第几行
-A/-B/-C显示匹配行的后文/前文/前后文看异常栈的上下文
-i忽略大小写搜ID、id、Id都行
--include/--exclude指定/排除文件类型排查代码里某函数的所有调用

举例说明。假设你有一份Nginx的访问日志,要统计今天的500错误有多少条:

grep -c 'HTTP/1.1" 500' access.log

要看看哪些接口在报错,并且带上响应码和URI:

grep -E 'HTTP/1.1" (500|502|503)' access.log | awk '{print $7, $9}' | sort | uniq -c | sort -rn

这行脚本已经出现awk了——你会发现一旦涉及统计,grep就顶不住了,得交给后面的命令。但grep至少帮你把范围缩小到了错误请求。

2.3 grep的经典盲区:伪装成正则的字符串

我见过不少人写grep,正则里带着一堆反斜杠转义,最后发现压根匹配不到东西。比如要搜IP地址,有人会写:

grep "192\.168\.1\.100" /var/log/secure

这里有个细节,.在正则里是"匹配任意字符",所以要匹配字面意义上的点必须转义。如果不加反斜杠,192.168.1.100这个模式甚至能匹配到192x168y1z100这样的字符串。同理,要匹配包含[error]标签的行,方括号在正则里是字符集的含义,也必须转义:

grep '\[error\]' /var/log/app.log

有个实用技巧:如果你要匹配的是纯文本、不带正则含义,直接加-F参数把模式当固定字符串处理,省去转义的麻烦,性能也更好。比如搜一堆订单号列表,建议把订单号放在文件里,用-f指定模式文件批量匹配:

grep -f order_ids.txt pay.log

顺便说一句,日常写正则,建议用单引号把模式包起来。双引号里$`\这些会被shell先解释掉,很容易产生诡异的结果。单引号内部是原始字符串,正则里的特殊符号不会被shell伤害到。这是一个极小但极其影响排查效率的细节。

3. sed:精准手术刀,文本增删改查样样行

3.1 先建立"流式处理"的心智模型

sed全称Stream Editor,流编辑器。它不像vim那样打开整个文件让你人机交互,而是逐行读取内容、按你给的规则处理、然后输出,处理完一行丢一行,内存里永远只保留当前行。这就是它能处理超大文件的底气。

sed的基本语法结构是:

sed '寻址+动作' 文件

理解"寻址"是关键:你想让sed对哪些行执行操作?"动作":你要对这些行做什么?

最常见的动作是替换s

sed 's/old/new/' file

注意,这条命令默认只替换每一行第一个匹配到的old。要替换一行内所有的old,必须在命令尾部加g修饰符:

sed 's/old/new/g' file

这个g是global的意思,很多新手会漏掉这个后缀,导致行内有多个匹配时只替换了第一个,排查半天还以为是数据问题。老实说,我基本每次都带g,不带g的场景极其罕见。

3.2 精准的寻址方式

如果我们只想改特定范围的行,需要在s///前面加寻址条件。

按行号:把第2行的foo改成bar:

sed '2s/foo/bar/'

按行号范围:第2到第5行:

sed '2,5s/foo/bar/'

按行号加步长:从第1行开始每隔3行处理一次:

sed '1~3s/foo/bar/'

按正则寻址:匹配到的行再执行替换:

sed '/pattern/s/foo/bar/'

按正则范围:从匹配到start的行开始,到匹配到end的行结束,闭区间:

sed '/start/,/end/s/foo/bar/'

这个范围寻址在提取日志时间段的时候特别有用。比如日志里每个请求都有=== BEGIN REQUEST ====== END REQUEST ===标记,你想把所有请求里某个字段的格式统一,一条命令就完成,不用手动画范围。

3.3 删除、追加、插入,不只是替换

替换只是sed的一部分能力。删行用d,在某行前插入用i,在某行后追加用a

删掉所有空行:

sed '/^$/d' file

删掉第3行:

sed '3d' file

删掉配置文件中所有注释行和空行——这个我常用来清理nginx.conf:

sed -e '/^#/d' -e '/^$/d' nginx.conf

在第5行后面追加一行worker_processes 4;

sed '5a worker_processes 4;' nginx.conf

a命令后面跟的内容中,多个单词不需要加引号,但如果要追加的内容本身就包含空格和特殊符号,建议用双引号包住整个表达式,再在内部用转义处理。实际使用中,我更喜欢把ai配合地址范围来做配置文件模板的批量生成,这是sed最有生产力的地方,后面综合实战会讲到。

3.4 sed -i的杀伤力和正确姿势

-i参数表示原地修改文件,这是sed最危险也最常用的特性。危险在于:一旦执行,原文件直接被改写,没有后悔药。我见过有人把生产环境的配置路径改错了,导致服务起不来,最后只能翻备份。

正确姿势分两种。第一,改之前先不写-i,把输出结果另存为临时文件检查一遍:

sed 's/old/new/g' app.conf > app.conf.tmp diff app.conf app.conf.tmp

确认无误再真正改。第二,用-i的时候带备份后缀,让sed自动生成备份文件:

sed -i.bak 's/old/new/g' app.conf

这样会生成一个app.conf.bak,改错了还能立刻回滚。等运行一段时间确认稳定了,再清理掉备份文件。我真的建议所有人都养成这个习惯,特别是在生产环境批量修改配置的时候,一条-i.bak能避免99%的后悔场景。

3.5 sed的隐藏用法:打印与退出

除了改文件,sed也常用来"查看"文件。配合-n参数,sed不做默认的全文输出,只打印你指定的行:

sed -n '20,30p' app.log

打印第20到30行,这在排查日志时比headtail方便得多。再配一个实用技巧,打印某个时间点到结束的所有行:

sed -n '/2024-06-15 10:00:00/,$p' app.log

$代表最后一行,整个命令的意思是从匹配到指定时间戳的行开始打印到文件末尾。这个过程在任何文本编辑器里都得处理半天,sed是瞬时的。

还有q命令,读到匹配的行立即退出。在扫描超大文件时,找到目标就停,能节省大量时间:

sed -n '/FATAL ERROR/q' app.log

虽然head也能配合管道完成,但sed这套逻辑更统一,理解起来不费劲。

4. awk:你以为它在处理文本,其实它是一门迷你语言

4.1 awk的数据透视表本质

很多新手学awk会被它的语法吓退,说"这跟C语言似的"。但换个角度理解:awk本质上是一个针对"行和列"的数据处理工具,类似于你在Excel里对一张表做筛选、求和、透视,只不过这张表可能有几百万行。

awk的基本结构是:

awk '模式 {动作}' 文件

它逐行读取文件,把每一行按分隔符拆分成N个字段,默认按空白字符(空格和Tab)分隔。$1代表第一个字段,$2代表第二个,$0代表整行,NF代表一行有多少个字段,NR代表当前是第几行。

比如要打印一个文件的第一列和第三列:

awk '{print $1, $3}' data.txt

这行命令的思维模式跟SQL的SELECT很像,print $1, $3就是选取列。如果想加个where条件,在动作前加一个模式:

awk '$3 > 100 {print $1, $3}' data.txt

第三列大于100的行才输出,这不就是WHERE子句吗。

4.2 定分隔符:别让默认空白坑了你

默认分隔符是空白,但很多日志和配置文件的列是用逗号、冒号、竖线分隔的。这时候要用-F指定分隔符。

awk -F',' '{print $1, $2}' data.csv awk -F: '{print $1, $3}' /etc/passwd

举一个真实例子。假设你有一份接口响应时间日志,格式是接口名|耗时ms|状态码

awk -F'|' '$3==200 {sum+=$2; count++} END {print "平均耗时:", sum/count, "ms"}' api.log

这条命令的意思是:以|为分隔符,取出状态码为200的行,累加耗时到sum,行数累加到count,最后在END块里算平均值。有SQL基础的人一眼就能看出,这相当于SELECT AVG(耗时) FROM 日志 WHERE 状态码=200

awk里有个执行顺序的概念:BEGIN块在读取文件之前执行,END块在所有行处理完之后执行,中间的模式{动作}对每一行执行一次。这三个块是你组织awk逻辑的基本骨架。

4.3 内建变量体系:NR、NF、FS、OFS

awk自带一套变量,掌握了它们几乎就能解决80%的统计需求:

变量含义典型用法
NR已经读过的记录数(行号)NR==1判断表头
NF当前行的字段数量过滤字段异常的脏数据
FS输入字段分隔符,同-FBEGIN{FS=","}
OFS输出字段分隔符控制print输出的分隔样式
$0整行原样处理
$n第n个字段核心操作对象

举个例子,如果想把csv格式转成tsv格式,只需要设置OFS:

awk -F',' 'BEGIN{OFS="\t"} {print $1, $2, $3}' data.csv

还有个高频需求:跳过表头。用NR>1作为模式,第一行不处理:

awk -F',' 'NR>1 {print $2}' data.csv

或者不打印表头,但用表头做列名映射——这个后面实战部分展开。

4.4 printf:让输出脱胎换骨

很多人不知道awk里最值得认真学的是printf,它能让你的输出对齐、格式化、完全可控。

对比一下:

awk '{print $1, $2}' data.txt awk '{printf "%-20s %8.2f\n", $1, $2}' data.txt

第一列左对齐占20个字符宽,第二列右对齐占8个字符、保留两位小数。这在生成报表的时候简直是神器,输出不再是乱七八糟的挤在一起,而是清爽的表格质感。

printf的格式控制符沿用的是C语言那套:%s字符串、%d整数、%f浮点数,-表示左对齐,数字表示宽度,.2表示小数位数。刚开始不要求全记住,遇到需求的时候查一下格式说明就够用,但要记得awk里有printf这么个东西,否则你会在拼字符串的路上越走越远。

4.5 awk的数组与统计:group by其实很简单

awk支持关联数组,索引可以是字符串。这是它做分组统计的核心能力。

来一个经典需求:统计访问日志里每个IP的访问次数,按次数降序排列。

awk '{count[$1]++} END {for (ip in count) print ip, count[ip]}' access.log | sort -k2 -rn

这个需求SQL里叫GROUP BY,awk里就是用一个数组count,以IP为键、以累加数值为值。END块里遍历数组打印结果,再用sort做排序。

再进阶一点:统计每个接口的平均响应时间、最大响应时间、最小响应时间。

awk -F'|' '{sum[$1]+=$2; cnt[$1]++; if($2>max[$1]) max[$1]=$2; if(min[$1]=="" || $2<min[$1]) min[$1]=$2} END {for (api in sum) printf "%s 调用%d次 平均%.2f 最大%d 最小%d\n", api, cnt[api], sum[api]/cnt[api], max[api], min[api]}' api.log | sort -k3 -rn

注意min[$1]==""这个判断:因为awk变量默认值是空字符串/0,如果不加这个判断,第一个值进来跟空比较时会出错。这也是awk数组统计里最隐蔽的坑之一。

4.6 awk处理日志的时间段统计

再分享一个运营经常会问到的需求:统计某个时间段内的请求量。日志格式的第一列是时间戳,格式为2024-06-15 10:23:45

awk '$1=="2024-06-15" && $2>="10:00:00" && $2<="11:00:00" {count++} END {print count}' access.log

更通用的做法是配合match函数提取时间字段做范围判断,不过如果时间恰好是独立列,直接字符串比较效率最高,因为awk的字典序比较对固定格式的时间字符串是天然正确的。前提是时间格式必须统一,如YYYY-MM-DD HH:MM:SS,否则建议先把时间字段解析成时间戳再比。

需要提醒的是,字符串比较跟数字比较在awk里是有区别的。如果字段是纯数字,建议先+0强制转成数值再比,否则可能因为字符串比较而出现100 < 20这种违背直觉的结果。这也是awk新手最容易踩的隐性坑。

5. 综合实战:三剑客组合拳

实战才见真章。接下来我给几个我之前在项目中用过的、高频到几乎每周都用的组合场景,你可以对照着敲一遍。

5.1 场景一:统计日志中错误码的Top 10

假设Java服务日志格式里有ERROR_CODE=500ERROR_CODE=404这样的片段,我想看今天报得最多的前10个错误码。

grep "$(date +%Y-%m-%d)" app.log | grep -o 'ERROR_CODE=[0-9]*' | sort | uniq -c | sort -rn | head -10

这条链路的逻辑:

  1. grep按今天的日期筛出当天日志;
  2. -o只提取ERROR_CODE=503这样的片段,不输出整行,这样后续统计不受其他字段干扰;
  3. sort对提取到的错误码排序(sort是管道里一个不起眼但极其重要的中间步骤,uniq只能合并相邻的重复行,必须先sort);
  4. uniq -c 统计每个错误码出现次数;
  5. sort -rn 按次数降序;
  6. head 10取前10。

uniq这个命令虽然不是三剑客,但它和grep组合频率极高,请务必记住配套的sort使用。坑点就在这:不sort直接uniq,结果是错的。

5.2 场景二:批量修改多个配置文件并自动备份

线上N台机器,每个应用目录下有个config.yaml,需要把数据库连接串从旧的改成新的,同时保留备份。

find /opt/app -name "config.yaml" -exec sed -i.bak 's/jdbc:mysql://old-host:3306/jdbc:mysql://new-host:3306/g' {} \;

分解一下:

  1. find找到所有目标文件;
  2. -exec对每个文件执行后面的大括号{}被替换为文件名;
  3. sed用-i.bak做原地替换并留备份;
  4. 末尾的\;告诉find命令结束。

这个命令我实测在处理几十台机器、几百个配置文件的场景下,执行时间在秒级。当然,如果是管理大规模集群,更推荐Ansible这类工具,但一次性应急处理,这行命令已经完全够用了。

5.3 场景三:日志清洗后生成运维报表

有这样一个场景:业务打点日志,每行是用户ID|城市|渠道|页面|停留时长,需要生成一份"每个城市、每个渠道的PV量、平均停留时长"的报表,并保存为csv用于后续分析。

awk -F'|' 'NR>1 {pv[$2 FS $3]++; time[$2 FS $3]+=$5} END {for (k in pv) {split(k, arr, FS); printf "%s,%s,%d,%.2f\n", arr[1], arr[2], pv[k], time[k]/pv[k]}}' business.log > report.csv

这条命令的关键点:

  1. NR>1是为了跳过第一行表头,假设日志有表头用户ID|城市|渠道|页面|时长
  2. $2 FS $3是把城市和渠道拼成一个复合键,FS在这里作为连接符,保证key唯一;
  3. split函数在END块里把复合键重新拆成两个字段用于输出;
  4. printf直接输出csv格式,逗号分隔。

sound复杂,但思路无非四步:定义key、累加、累计时长、END里格式化输出。这是awk最经典的分组汇总套路,建议反复练习到不用查文档也能写出来的程度。

5.4 场景四:批量压缩历史日志并只保留最近三天

经常有日志目录快撑爆磁盘的情况,在不影响最近排查的前提下,需要把7天前的日志压缩,3天前的压缩包删除。用grep配合find、xargs也能做,但既然说三剑客组合,我演示一个带awk的版本:

find /data/logs -name "*.log" -mtime +7 | awk '{print "gzip", $0}' | bash find /data/logs -name "*.log.gz" -mtime +3 -delete

这个例子不算正中三剑客的核心用法,但演示了awk在管道里做"命令字符串构造"的灵活之处——awk '{print "gzip", $0}'把find列出的文件名包装成gzip命令,再用管道交给bash执行。这种"构造命令再执行"的模式应用很广,远比手写for循环来得简洁。

注意生产环境用这种写法前一定要先在测试目录验证,因为awk构造出来什么命令你就执行什么,一个字段拼接错误可能在线上批量压错文件。安全的做法是先不接| bash,让命令打印出来检查一遍。

5.5 awk带表头的报表优雅输出

最后补充一个我经常被问到的需求:awk统计结果想带表头,怎么做才好看。

awk -F'|' 'BEGIN{printf "%-20s %-10s %10s\n", "接口", "状态码", "次数"} {cnt[$1 FS $3]++} END {for (k in cnt) {split(k, arr, FS); printf "%-20s %-10s %10d\n", arr[1], arr[2], cnt[k]}}' api.log

这里用到了BEGIN块:在读取任何数据之前先打印表头,用printf的格式控制符%-20s控制各列宽度。表头和数据用了同样的格式说明,列就能对齐。我见过很多人用print拼凑输出,报表乱成一团,每次都要在Excel里重新分列。printf才是awk输出的正确打开方式。

还有一个很多人想知道的细节:awk的END块输出顺序是随机的(因为awk底层用哈希表存储数组),如果希望结果有序,就按我前面的做法在awk之后再接管道,用sort -k指定排序字段。这就是命令组合的妙处:awk负责算,sort负责排,各司其职,谁也不越俎代庖。

6. 省心提示:哪些命令必须配管道

最后聊一点我自己的习惯。你可能会发现,前面所有复杂一点的示例,几乎都离不开管道。原因在于,三剑客各有所长,但都不是全能的,只有组合起来才是一个完整的工具链。

我自己的固定搭配套路是:

  • 第一步定位范围:用find或grep把要处理的文件、行缩小。
  • 第二步提取内容:用grep -o或awk把需要的字段抠出来。
  • 第三步统计变换:用awk做求和、平均、分组。
  • 第四步整理展示:用sort、uniq -c、head做排序和topN,必要时用awk的printf对齐输出。

这套流程适用于绝大多数的文本排查和报表生成需求。建议你找个真实一点的日志文件,从这四步入手,多练几次让肌肉记忆形成。等到哪一天你遇到问题第一反应不再是"打开Notepad++查找",而是顺手敲出带管道符的命令,三剑客就算真正上手了。

敢直接在命令行里改生产配置、敢对几GB的日志做实时统计,这份底气不是背参数背出来的,是靠踩坑、试错、看输出结果慢慢磨出来的。工具永远是死的,思路才是活的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 5:16:34

锥形光纤COMSOL建模与模式传输优化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 5:16:28

Hadoop生态组件选型与集群搭建实战:从HDFS到Hive的全流程指南

距今为止&#xff0c;我经手搭建过的大数据平台不说上百套&#xff0c;也差不多覆盖了从传统制造业到互联网电商的多个行业场景。这个标题里最值得玩的其实是“排行榜”三个字——市面上讲Hadoop搭建的教程一抓一大把&#xff0c;但真正从企业级落地视角去捋清楚“该选哪些组件…

作者头像 李华
网站建设 2026/9/14 5:16:13

医院问诊微信小程序模板源码改造:前端开发完整指南

简介&#xff1a;面向开发者的医院问诊微信小程序前端模板源码&#xff0c;可用于快速搭建在线医疗咨询平台&#xff0c;覆盖预约挂号、即时问诊、健康资讯与个人中心等业务模块&#xff0c;适合前端工程师或小程序初学者借鉴实践。zip压缩包共508个文件、约894KB&#xff0c;主…

作者头像 李华
网站建设 2026/9/14 5:14:23

工单状态设计实战:状态定义、流转规则与踩坑全解析

工单状态这个东西&#xff0c;听起来就是四个字&#xff0c;好像谁都知道怎么回事&#xff0c;但你真去把一个工单系统的状态字段理清楚&#xff0c;会发现里面全是坑。不同部门对“处理中”的理解可能完全不一样&#xff0c;运营说的“待处理”和技术说的“待处理”根本是两码…

作者头像 李华
网站建设 2026/9/14 5:13:57

DeepSeek V4.1 Flash 部署实战:显存估算与 vLLM/SGLang 启动指南

这几天社区里关于 DeepSeek V4.1 Flash 的讨论明显多起来了&#xff0c;很多人已经在问同一件事&#xff1a;这个模型到底需要多大的显存&#xff0c;手里现有的卡能不能跑起来。我按 DeepSeek 近几代模型的发布规律和推理引擎的更新节奏判断&#xff0c;Flash 这种主打低延迟、…

作者头像 李华