返回首页

grep / awk / sed 运维实战三件套 — 从日志排查到配置修改

📅 创建于 2026-07-21 🔄 更新于 2026-07-21 📝 838 字

来源:运维派 | 发布日期:2026-07-14

grep / awk / sed 运维实战三件套

定位: grep 负责"找",sed 负责"改",awk 负责"算"。理解定位后,组合命令不再是死记硬背,而是能推导出来。

本文聚焦运维高频场景,按"实际场景 → 核心原理 → 验证实验 → 常见误区 → 工程建议"展开。环境为 GNU grep/sed/gawk(CentOS/Ubuntu 均适用)。


一、grep:把问题行"找"出来

核心原理

逐行读取输入,用正则匹配每一行,匹配成功则输出该行。grep 天生不能跨行匹配——多行需用 -z 或换 awk/perl。

常用参数速查

参数 作用
-i 忽略大小写
-v 反向匹配(排除)
-n 显示行号
-c 只统计匹配行数
-r / -R 递归查找目录
-l 只列出含匹配内容的文件名
-E 扩展正则(等价 egrep)
-A n 显示匹配行之后 n 行
-B n 显示匹配行之前 n 行
-C n 显示匹配行前后各 n 行
-w 整词匹配(避免子串误匹配)
-o 只输出匹配到的部分

实战场景

看上下文找报错触发原因:

grep -C 5 "OutOfMemoryError" /var/log/app/app.log

输出示例:

2026-07-14 09:12:01 INFO  Processing batch job id=8821
2026-07-14 09:12:03 INFO  Loaded 500000 records into memory
2026-07-14 09:12:05 WARN  Heap usage at 92%
2026-07-14 09:12:06 ERROR OutOfMemoryError: Java heap space
2026-07-14 09:12:06 ERROR Thread [batch-worker-3] terminated
2026-07-14 09:12:07 INFO  GC triggered, heap usage dropped to 45%

上下文直接给出根因:加载 50 万条记录 → 堆 92% → OOM。

统计某类错误次数:

grep -c "Connection refused" /var/log/app/app.log
# 输出: 47

排除已知干扰信息:

grep "ERROR" /var/log/app/app.log | grep -v "DeprecationWarning"

递归查找配置项在哪些文件:

grep -rn "max_connections" /etc/mysql/
# 输出: /etc/mysql/mysql.conf.d/mysqld.cnf:23:max_connections = 500

验证实验:正则踩坑

坑 1:BRE vs ERE 的"或"匹配

# 误以为 | 生效——实际是匹配了字面 timeout
echo "connection timeout" | grep "timeout|refused"
# 输出: connection timeout

# 换一个只含 refused 的输入,验证失败
echo "connection refused" | grep "timeout|refused"
# 输出: (空)

# 正确写法:加 -E
echo "connection refused" | grep -E "timeout|refused"
# 输出: connection refused

坑 2:特殊字符未转义

# 错误:. 匹配任意字符,会误匹配 192a168x1y1
grep "192.168.1.1" access.log

# 正确:转义点号
grep "192\.168\.1\.1" access.log

常见误区

  • 误区一:以为 grep 能跨行匹配。 逐行处理,跨行需用 -z(可读性差)或换 awk/perl。
  • 误区二:大文件多层管道性能差。 grep A file | grep B | grep C 可合并为 grep -E "A.*B.*C"(固定顺序时)或一次性 awk。
  • 误区三:忽视 -w 导致子串误匹配。 查端口 80 不加 -w 会匹配到 8080、1780。

二、sed:流式修改文本

核心原理

逐行读取 → 执行编辑命令 → 输出结果。默认不修改原文件,只输出到 stdout——这是生产排查的安全边界。

基础替换

# 只替换每行第一个匹配
sed 's/旧内容/新内容/' 文件名

# 替换该行所有匹配
sed 's/旧内容/新内容/g' 文件名

生产场景:批量改配置

先预览,再改(铁律):

# 1. 先不加 -i 预览
sed 's/test.example.com/prod.example.com/g' nginx.conf

# 2. 确认无误后 -i 修改,同时备份
cp nginx.conf nginx.conf.bak.$(date +%Y%m%d%H%M%S)
sed -i 's/test.example.com/prod.example.com/g' nginx.conf

# 或者用 GNU sed 的 -i.bak(注意 -i 和后缀之间不能有空格)
sed -i.bak 's/test.example.com/prod.example.com/g' nginx.conf
# 生成 nginx.conf.bak + 修改 nginx.conf

删除空行和注释行:

sed '/^$/d' 文件名              # 删除空行
sed '/^#/d' 文件名              # 删除 # 开头注释行

只看文件第 10-20 行:

sed -n '10,20p' 文件名

按关键字定位区间替换(配置块内修改):

sed '/\[section_a\]/,/^\[/ s/enabled=false/enabled=true/' config.ini

多个替换组合:

sed -e 's/foo/bar/g' -e 's/baz/qux/g' 文件名

验证实验:分隔符与捕获组

自定义分隔符(避免路径转义):

# 难读:路径中 / 需要大量转义
sed 's/\/opt\/old\/path/\/opt\/new\/path/g' 文件名

# 改用 # 或 | 作分隔符,等价但清晰
sed 's#/opt/old/path#/opt/new/path#g' 文件名

捕获组 + 反向引用:

echo "status=200" | sed -E 's/([a-z]+)=([0-9]+)/\2 (\1)/'
# 输出: 200 (status)

常见误区

  • 误区一:直接用 -i 改生产配置不备份。 正则偏差 → 内容永久破坏无回退。任何 -i 前先不加 -i 预览,或用 -i.bak
  • 误区二:贪婪匹配误替换。 s/<.*>//g 清除 HTML 标签,多组标签时会从第一个 < 匹配到最后一个 >,把中间文本也删掉。POSIX 正则不支持非贪婪量词,此类需求换 awk 或 perl。
  • 误区三:用 sed 处理列。 sed 面向"行"和"模式匹配",按列/按字段处理应交给 awk。

三、awk:按字段统计计算

核心原理

每行按分隔符(默认连续空白)切分字段:$1=第一列,$NF=最后一列,$0=整行。处理模型是"模式-动作"对:awk '模式 {动作}' 文件

基础用法

提取列:

awk '{print $1}' access.log

统计每个 IP 访问次数(最经典场景):

awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

输出示例:

   1823 203.0.113.15
    967 198.51.100.22
    412 192.0.2.8

awk 只负责提取第一列,统计排序交给 sort/uniq——各工具做自己擅长的事。

指定分隔符:

awk -F: '{print $1, $NF}' /etc/passwd
# 输出: root /bin/bash

按条件筛选(磁盘使用率 > 80%):

df -h | awk 'NR>1 {gsub("%","",$5); if ($5+0 > 80) print $0}'
# 输出: /dev/mapper/vg-root  50G  42G  8.0G  84% /

NR>1 跳过表头;gsub("%","",$5) 去掉百分号;$5+0 强制转数值(awk 隐式转数值技巧)。

统计总和与平均值:

awk '{sum+=$7; count++} END {print "总请求数:", count, "平均耗时(ms):", sum/count}' app_access.log
# 输出: 总请求数: 15420 平均耗时(ms): 87.3

END 块:处理完所有行后执行一次,常用于汇总。BEGIN 块:处理前行前执行,常用于初始化或打印表头。

验证实验:字段分隔符细节

默认分隔符 = 连续空白(多个空格/tab 视为一个分隔符):

echo "a    b   c" | awk '{print $2}'
# 输出: b

显式 -F" " 指定单个空格作分隔符:

echo "a    b   c" | awk -F" " '{print $2}'
# 输出: (空)

连续空格被当成多个分隔符,第二列变空。处理 ps、df、free 等对齐输出时,必须用默认分隔符,不能额外指定 -F" "

常见误区

  • 误区一:处理 ps/free 对齐输出用固定分隔符导致列错位。 用 awk 默认连续空白分隔规则。
  • 误区二:字符串比较用数值比较符。 if ($8 == "Z") 判断僵尸进程——必须加引号,误写成裸字符会被当成变量名(值为空),判断永远不成立。
  • 误区三:忽视 $NF 在字段不固定日志里的不稳定性。 某些字段缺失时总字段数变化,固定序号 $7 可能取错列。先 awk '{print NF}' 抽样确认字段数稳定性,再决定用固定序号还是 $(NF-1) 相对引用。

四、三者组合:完整排查场景

场景:从 Nginx 日志找 5xx 请求,统计出现次数最多的 URL(最近一小时内)。

CURRENT_HOUR=$(date '+%Y-%m-%d %H')
grep "^\[${CURRENT_HOUR}" access.log | \
awk '$9 ~ /^5[0-9][0-9]$/ {print $7}' | \
sort | uniq -c | sort -rn | head -10

命令拆解:

  1. grep 按时间前缀粗筛——性能优化关键,不让 awk 处理全量日志
  2. awk 正则匹配第 9 列(状态码)判断 5xx,命中则输出第 7 列(请求路径)
  3. sort | uniq -c | sort -rn 统计次数降序排列

⚠️ 第 7 列、第 9 列具体对应哪个字段,完全取决于 Nginx log_format 定义顺序。排查前必须先确认:nginx -T | grep -A 5 "log_format"。不能凭经验假设列号。


五、工程建议

  1. 批量修改前先验证: sed -i 前先在测试文件验证正则匹配范围,生产操作前一定备份。
  2. 大文件过滤前置: 几个 GB 日志,先用 grep 粗筛缩小数据量,再交给 awk 精细处理——显著减少耗时。
  3. 复杂逻辑写成脚本: 排查逻辑需维护复用、或一行命令难以阅读时,写成带注释的脚本文件保存,减少手误。
  4. 字段列号以实际配置为准: 日志分析的列号、分隔符要以实际 log_format 为准,不要凭记忆套用。
  5. 管道组合优于单工具硬扛: grep 找、sed 改、awk 算——优先用管道组合各工具优势,可读性和可维护性同样重要。

关联页面

页面关联点
linux-awk-sed-text-processingawk + sed 批量文本处理实战,含 8 大 Nginx 日志分析场景和完整分析脚本
linux-essential-commands-referenceLinux 运维 30 个高频命令速查
nginx-log-analysis-troubleshooting-guideNginx 日志分析 + 4xx/5xx/超时故障排查
nginx-troubleshooting-methodology-8-stepsNginx 8 步排障方法论 + 应急快查表
linux-perf-troubleshooting-handbookLinux 服务器性能排查实战手册