运维每天都用的 grep / awk / sed 三条命令怎么分工?本文用真实日志和 CSV 文件演示:grep 做过滤、awk 做字段切片与统计、sed 做替换和编辑,附每条命令最常用参数和坑点。
适用场景一句话划分
校验环境:Debian 12 (GNU grep 3.11 / GNU awk 5.2.1 / GNU sed 4.9)
运维、后端、数据同学每天都在写的三条命令,分工非常明确:
| 命令 | 作用 | 一句话判断 |
|---|---|---|
| grep | 从输入中挑行(行过滤) | 只想看到/排除包含某字符串的行 |
| awk | 按字段切分、取值、统计 | 需要把每行按列切开,取第 3 列、求和、分组 |
| sed | 对每行改内容(替换/删除/插入) | 想做全局替换、批量改配置、删某些行 |
下面用一份真实 Nginx access log(精简版)做完整示例。示例文件 access.log:
192.168.1.10 - - [09/Aug/2026:10:23:11 +0800] "GET /docs/git/git-stash HTTP/1.1" 200 3421 "-" "Mozilla/5.0"
10.0.0.5 - - [09/Aug/2026:10:23:12 +0800] "POST /api/login HTTP/1.1" 401 128 "-" "curl/8.5.0"
192.168.1.10 - - [09/Aug/2026:10:23:13 +0800] "GET /docs/linux/grep-awk-sed HTTP/1.1" 200 9820 "-" "Mozilla/5.0"
45.88.12.3 - - [09/Aug/2026:10:23:14 +0800] "GET /wp-login.php HTTP/1.1" 404 153 "-" "python-requests/2.31"
10.0.0.5 - - [09/Aug/2026:10:23:15 +0800] "POST /api/login HTTP/1.1" 200 512 "-" "curl/8.5.0"
grep:行过滤(挑行)
最常用参数
| 参数 | 作用 |
|---|---|
-E | 启用扩展正则(ERE),支持 ` |
-F | 按固定字符串匹配,不解析正则。匹配 IP、URL、路径时强烈推荐 |
-v | 取反,输出不匹配的行 |
-n | 显示行号 |
-c | 只输出匹配行数 |
-i | 忽略大小写 |
-r | 递归搜目录下所有文件 |
-o | 只输出匹配到的部分(不是整行) |
真实示例
# 1. 看所有 4xx/5xx 请求
grep -E '" (4[0-9]{2}|5[0-9]{2}) ' access.log
# 2. 看所有非爬虫(排除 UA 里带 curl 和 python-requests 的行)
grep -vE '" (curl|python-requests)/' access.log
# 3. 按 IP 精确搜(用 -F 防止 . 被当成任意字符正则)
grep -F '10.0.0.5' access.log
# 4. 数 200 状态码有多少条
grep -cE '" 200 ' access.log
# 输出:2
最常见坑
没加 -F 把 IP 的 . 当正则匹配了:grep '10.0.0.5' log 会匹配 102030.5(. 匹配任意字符)。
匹配 IP / 文件路径 / URL / 版本号(1.2.3) 这种自带元字符的字符串,一律加 -F。
awk:字段切片与统计
awk 的核心模型:一条输入 = 一条记录(record),一条记录按分隔符切成多个字段,$1 第 1 个字段、$2 第 2 个……$NF 最后一个字段、$0 整行。
最常用内建变量
| 变量 | 含义 |
|---|---|
FS | 输入字段分隔符(默认「任意空白字符」空格/Tab 都算一个) |
OFS | 输出字段分隔符(默认空格) |
NR | 当前第几条记录(行号) |
NF | 当前记录的字段数(所以 $NF 是最后 1 列) |
语法:awk '条件 {动作}' 文件,条件没写=对每行都做。
真实示例
# 1. 取出每条 log 的 IP + 状态码(第 1 列 + 第 9 列)
awk '{print $1, $9}' access.log
# 输出:
# 192.168.1.10 200
# 10.0.0.5 401
# ...
# 2. 自定义分隔符:取请求路径(按双引号切,第 2 段是 "GET /xxx HTTP/1.1",再按空格切第 2 个)
awk -F'"' '{print $2}' access.log | awk '{print $2}'
# 输出:
# /docs/git/git-stash
# /api/login
# /docs/linux/grep-awk-sed
# /wp-login.php
# /api/login
# 3. 只保留状态码非 200 的行,输出 IP 和 状态码(用条件)
awk '$9 != 200 {print $1, $9}' access.log
# 输出:
# 10.0.0.5 401
# 45.88.12.3 404
# 4. 统计每个状态码出现的次数(分组 sum,awk 最强项,不要再去写 Python)
awk '{cnt[$9]++} END {for (code in cnt) print code, cnt[code]}' access.log
# 输出示例:
# 200 2
# 401 1
# 404 1
# 5. 字段分隔符改成冒号(比如拿 /etc/passwd 的 shell)
awk -F: '$7 == "/bin/bash" {print $1, $6}' /etc/passwd
最常见坑
- 用
,分隔print的多个参数时输出之间会插空格(OFS),想连写就直接拼:print $1 ":" $9 END {}里的代码是所有行处理完才执行,写在外面的代码每行都会跑——很多人把统计逻辑没写进 END 导致每行都打印一次
sed:替换和编辑
sed = Stream Editor,对输入的每一行按脚本顺序执行指令,然后输出。绝大多数场景只用 s(substitute 替换)指令。
s 指令语法
s/要匹配的正则/替换内容/修饰符
修饰符:
- 不加:只替换每行第一个匹配
g:global,每行全部替换(95% 场景带这个)i:忽略大小写p:打印被替换的行(常和-n搭配)
分隔符可选:不是只能用 /
只要三个字符一致即可。替换内容含 URL/路径时推荐用 # @ |:
# 写法 1:全是 / 要疯狂转义(不要这么写)
sed 's/\/var\/www\/html/\/srv\/www/g' nginx.conf
# 写法 2:用 # 当分隔符(推荐)
sed 's#/var/www/html#/srv/www#g' nginx.conf
真实示例
# 1. 把 access.log 里所有 HTTP/1.1 改成 HTTP/2(只是 stdout 输出,不改原文件)
sed 's#HTTP/1.1#HTTP/2#g' access.log
# 2. 原地修改文件(-i,Linux 上 GNU sed 直接写;macOS BSD sed 需要 -i '')
sed -i 's#127.0.0.1:8080#0.0.0.0:3000#g' .env
# 3. 删除包含 wp-login.php 的行(常见扫垃圾请求行)
sed '/wp-login\.php/d' access.log
# 4. 只打印第 3 到第 5 行
sed -n '3,5p' access.log
最常见坑
-i 参数在 macOS 上和 Linux 上不一致-i 后面必须跟备份后缀,想不备份写 -i '';GNU sed 直接 -i 就行。
如果脚本要跨平台,统一写成:
# GNU sed + BSD sed 都能跑的写法:先备份再删备份
sed -i.bak 's/foo/bar/g' file && rm file.bak
组合管道示例:3 条一起用
需求:统计 access.log 里,排除爬虫 UA,非 200 的请求中,按请求路径分组取 Top 3。
grep -vE '" (curl|python-requests)/' access.log \
| awk '$9 != 200' \
| awk -F'"' '{print $2}' \
| awk '{print $2}' \
| sort \
| uniq -c \
| sort -rn \
| head -n 3
拆解:
grep -vE:先排除爬虫行awk '$9 != 200':拿非 200 行- 两个
awk -F'"'+ 空格切:抽出路径 sort | uniq -c:分组计数(经典写法)sort -rn:按计数从大到小head -n 3:取前 3
常见问题 FAQ
Q1. 想在配置文件里注释掉某一行(前面加 #),用 sed 怎么做?
# 把 listen 80; 这一行前面加 #
sed '/listen 80;/s#^#\##' nginx.conf
# 反向:取消注释(去掉行首的 #)
sed 's#^#\?\(listen 80;\)#\1#' nginx.conf
Q2. awk 里怎么写 if/else?要不要写 bash?
直接写就行。awk 是完整的迷你语言,别什么都去开 Python:
awk '{
if ($9 >= 500) print "5xx:", $0
else if ($9 >= 400) print "4xx:", $0
else print "ok:", $1
}' access.log
Q3. 我想让 grep 结果高亮关键词但不改变管道输出?
加 --color=always,在交互式终端很好用;管道里建议 auto(默认),否则 ANSI 颜色码会污染后面的 awk/sed 解析。
更正记录
- 2026-08-09:首版发布,校验环境 Debian 12 / GNU 三件套版本如上。