3. grep 与 awk 实战:快速过滤与精准提取

日志分析这活儿,说白了就两件事:找到你要的行,然后拿到你要的字段。grep 干第一件,awk 干第二件。这两兄弟配合好了,你处理日志的速度能快上十倍。

我记得刚带团队那会儿,有个同事花了一下午在 2GB 的访问日志里手工翻找某个 IP 的请求。我过去看了一眼,一行命令 3 秒出结果。他当时那个表情……嗯,从那以后我就要求组里每个人都必须把 grep 和 awk 练熟。

3.1 grep:日志过滤的第一把刀

grep 的基本用法大家都会,但真正用好它,你得掌握几个关键技巧。

3.1.1 常用参数速查

参数作用我常用的场景
-i忽略大小写ERROR、error、Error 全抓到
-v反向匹配排除心跳包之类的噪音行
-c只计数快速知道报了多少错
-n显示行号定位到具体位置去排查
-A/-B/-C上下文行看错误前后的日志,这个太重要了
-E扩展正则用 | 组合多个条件
-PPerl 正则处理复杂匹配时用

我的习惯:排查问题时,我几乎必加 -C 5。只看错误行本身往往不够,前后 5 行才能还原现场。你想想看,光看一句 "NullPointerException" 能看出什么?得看到它前面调了哪个方法才行。

3.1.2 实战:多条件过滤

工作中很少只查一个关键词。比如我要查支付模块的报错:

# 同时包含 "ERROR" 和 "payment"
grep -E "ERROR.*payment|payment.*ERROR" app.log

# 或者用管道更直观
grep "ERROR" app.log | grep "payment"

# 排除健康检查的噪音
grep "ERROR" app.log | grep -v "healthcheck"

我个人更推荐管道方式。虽然多打几个字符,但可读性好,后面想加条件直接再 pipe 一次就行。

3.1.3 正则匹配实战

日志里的 IP 地址、时间戳、UUID,用正则匹配最干净。

# 匹配所有 IPv4 地址
grep -E "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log

# 匹配 5xx 状态码
grep -E "\" [5][0-9]{2} " access.log

# 匹配特定时间窗口(比如 10:00 到 10:59)
grep -E "10:[0-5][0-9]:[0-5][0-9]" app.log

踩坑提醒:我曾经在生产环境上直接用 grep -P 处理一个超大文件,结果机器内存被打满。后来查了才知道,-P 在某些版本里会预加载整个文件到内存。大文件场景,老老实实用 -E 吧。

3.2 awk:字段提取的瑞士军刀

grep 帮你找到了行,awk 帮你从行里拿东西。awk 的哲学很简单:把一行按分隔符切开,然后你告诉它要第几块。

3.2.1 基础字段提取

拿 Nginx 访问日志举例,默认空格分隔:

# 打印 IP(第1列)和状态码(第9列)
awk '{print $1, $9}' access.log

# 指定分隔符为逗号(处理 CSV 日志)
awk -F',' '{print $2, $5}' data.csv

# 内置变量 NF 表示列数,$NF 是最后一列
awk '{print $NF}' access.log

我刚开始用 awk 时总记不住列号。后来养成了习惯:先 head -1 log | awk '{for(i=1;i<=NF;i++) print i": "$i}' 看一眼每列的编号,再写提取命令。这招省了不少事。

3.2.2 条件过滤 + 字段提取

这才是 awk 的真正威力——过滤和提取一步到位:

# 只提取 5xx 错误的请求路径
awk '$9 ~ /^5[0-9]{2}/ {print $7}' access.log

# 统计每个 IP 的请求次数
awk '{count[$1]++} END {for(ip in count) print ip, count[ip]}' access.log

# 提取响应时间超过 3 秒的请求
awk '$NF > 3 {print $1, $7, $NF}' access.log

核心思路:awk 的模式-动作结构(pattern {action})让你能同时做两件事——pattern 负责筛选行,action 负责处理字段。这比 grep 管道 awk 少一次 IO,处理大文件时性能差距很明显。

3.3 grep + awk 组合拳

单独用 grep 或 awk 都能干活,但组合起来才是真正的效率翻倍。

3.3.1 经典流水线模式

# 先 grep 粗筛,再 awk 精提
grep "ERROR" app.log | awk -F'|' '{print $2, $5}'

# 统计某个模块的错误类型分布
grep "payment" app.log | grep "ERROR" | awk '{print $5}' | sort | uniq -c | sort -rn

这个模式我每天都在用。先 grep 把数据量降下来,awk 处理起来就快得多。你想想看,1GB 的日志,grep 筛完可能只剩 10MB,后面的操作就轻松了。

3.3.2 实战:定位慢查询

有一次线上告警说数据库响应慢,我这么查的:

# 1. 找到所有慢查询日志行
grep "slow_query" mysql.log

# 2. 提取查询时间和 SQL 片段
grep "slow_query" mysql.log | awk '{print $1, $2, $NF}'

# 3. 只显示超过 5 秒的
grep "slow_query" mysql.log | awk '$NF > 5 {print $1, $2, $NF}'

# 4. 按时间排序,看最慢的
grep "slow_query" mysql.log | awk '$NF > 5 {print $1, $2, $NF}' | sort -k3 -rn | head -10

整个过程不到 30 秒,就找到了那个没加索引的全表扫描查询。要是靠人工翻日志……嗯,你懂的。

3.4 知识体系总览

下面这张图把 grep 和 awk 的核心用法串起来了,建议你保存下来当速查表用。

grep + awk 日志处理知识体系 grep 日志过滤 • 基础:-i, -v, -c, -n • 上下文:-A, -B, -C • 正则:-E 扩展, -P Perl • 多条件:管道组合 • 实战:IP/状态码/时间窗口 → 输出:匹配行 awk 字段提取 • 基础:$1, $NF, NF • 分隔符:-F 自定义 • 条件:pattern {action} • 聚合:数组统计 • 实战:慢查询/错误分布 → 输出:结构化字段 grep + awk 组合拳 grep 粗筛 → awk 精提 → sort/uniq 聚合 → head 取 Top 先降数据量,再精细处理,效率最大化 输出结果 错误分布 | 慢查询 Top10 | IP 统计 | 状态码聚合

3.5 避坑指南与效率技巧

最后分享几个我踩过的坑和总结的技巧:

  • 大文件别用 -P:Perl 正则虽然强大,但处理大文件时内存消耗惊人。我吃过一次亏,2GB 日志直接 OOM。
  • awk 里用双引号还是单引号?awk 程序体必须用单引号,否则 shell 会展开 $1 变量,结果全错。
  • 先看结构再写命令:用 head -5 log | cat -A 看看隐藏分隔符,避免被空格和制表符坑到。
  • 养成加 -C 的习惯:排查错误时,grep -C 5 "ERROR" 比光看错误行有用十倍。
  • 管道不是越多越好:超过 3 个管道可以考虑用 awk 一步完成,减少 IO 开销。

我的工作流:遇到线上问题,我通常先 grep -c "ERROR" app.log 看错误量,然后 grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c | sort -rn | head -5 看错误类型分布,最后 grep "具体错误" app.log | head -20 看详情。整个过程 1 分钟内搞定。

grep 和 awk 学好了,你处理日志的速度会有一个质的飞跃。别贪多,先把今天讲的这几个组合练熟,遇到实际问题时自然就知道怎么用了。