来源:运维派 | 发布日期: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
命令拆解:
grep按时间前缀粗筛——性能优化关键,不让 awk 处理全量日志awk正则匹配第 9 列(状态码)判断 5xx,命中则输出第 7 列(请求路径)sort | uniq -c | sort -rn统计次数降序排列
⚠️ 第 7 列、第 9 列具体对应哪个字段,完全取决于 Nginx
log_format定义顺序。排查前必须先确认:nginx -T | grep -A 5 "log_format"。不能凭经验假设列号。
五、工程建议
- 批量修改前先验证:
sed -i前先在测试文件验证正则匹配范围,生产操作前一定备份。 - 大文件过滤前置: 几个 GB 日志,先用
grep粗筛缩小数据量,再交给awk精细处理——显著减少耗时。 - 复杂逻辑写成脚本: 排查逻辑需维护复用、或一行命令难以阅读时,写成带注释的脚本文件保存,减少手误。
- 字段列号以实际配置为准: 日志分析的列号、分隔符要以实际
log_format为准,不要凭记忆套用。 - 管道组合优于单工具硬扛: grep 找、sed 改、awk 算——优先用管道组合各工具优势,可读性和可维护性同样重要。
关联页面
| 页面 | 关联点 |
|---|---|
| linux-awk-sed-text-processing | awk + sed 批量文本处理实战,含 8 大 Nginx 日志分析场景和完整分析脚本 |
| linux-essential-commands-reference | Linux 运维 30 个高频命令速查 |
| nginx-log-analysis-troubleshooting-guide | Nginx 日志分析 + 4xx/5xx/超时故障排查 |
| nginx-troubleshooting-methodology-8-steps | Nginx 8 步排障方法论 + 应急快查表 |
| linux-perf-troubleshooting-handbook | Linux 服务器性能排查实战手册 |