linux运维三剑客·(grep / sed / awk)
这仨老家伙从70年代活到现在,不是因为怀旧,是因为真特么好用。进容器里不一定有Python,但一定有它们。以下全是干活时磨出来的经验,不带水分。
1. grep —— 找东西的
基本操作
grep "error" app.log # 找errorgrep -i "error" app.log # 不分大小写grep -n "error" app.log # 告诉你哪一行
常用参数(记这5个就够了)
-C 5 :显示匹配行的上下5行(看报错必须带上下文)-v :反选,比如 grep -v "health" 把健康检查那堆垃圾过滤掉-c :只输出行数,grep -c error 数数今天崩了几次-r :递归搜目录,grep -r "listen 80" /etc/nginx/-o :只输出匹配到的部分,不输出整行(配合正则提取IP)
实战场景
1. 实时看日志,屏蔽噪音
tail -f app.log | grep -v "heartbeat" | grep -v "metrics"
用两个 -v 把烦人的心跳和监控刷屏过滤掉。
2. 找出所有包含旧IP的配置文件,只显示文件名
grep -rl "192.168.1.1" /etc/nginx/
-l 不显示内容,只显示文件名,专门喂给 xargs 用。
3. 从乱七八糟的日志里只提取IP地址
grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log | sort | uniq -c
-E 启用扩展正则,-o 只吐出IP,后面跟个 sort | uniq -c 直接统计访问量。
4. 看报错时一定要带上下文
grep -C 5 "OutOfMemoryError" catalina.out
别光搜那一行,真正的原因往往在前面三行。
踩坑提醒
- 用
+、?、| 这些正则符号时,记得 grep -E,不然匹配不到别怪我没说。 - 想搜
error 但不想搜 error.log 这种文件名?用 --exclude 或 --include 指定文件类型。
2. sed —— 改东西的(不用vim)
核心心法
sed 就是流式编辑器,一行一行处理,不打开文件也能改。
替换(最常用)
sed -i 's/old/new/g' file
- 加后缀备份:
sed -i.bak 's/old/new/g' file,改完后多一个 file.bak
替换的几种变形:
sed 's/old/new/' file # 只替换每行第一个匹配sed 's/old/new/2' file # 替换每行第二个匹配sed 's/old/new/g' file # 替换所有匹配
删除行
sed -i '/^$/d' file # 删除空行(^$ 表示空行)sed -i '/debug/d' file # 删除所有带debug的行sed -i '5d' file # 删除第5行sed -i '5,10d' file # 删除5到10行
插入行
sed -i '1i # 这是第一行' file # 在第一行前面插入sed -i '$a # 这是最后一行' file # 在最后一行后面追加sed -i '/server {/a\ listen 80;' nginx.conf # 匹配到server { 就追加一行
注意 a 和 i 后面的反斜杠和空格不能错,多行插入有固定格式,但上面这种单行用法最常用。
替换整行
sed -i '/^old_host=/c\new_host=10.0.0.1' .env
找到以 old_host= 开头的行,整行替换成 new_host=10.0.0.1。
多命令组合
sed -i -e 's/foo/bar/g' -e '/^$/d' file
用 -e 可以接多个操作。
踩坑提醒
- macOS 的 sed 跟 Linux 不一样:
-i 后面必须跟备份后缀,哪怕空也得写 -i ''。 - 替换路径时,分隔符
/ 跟路径冲突,可以用 # 或 | 代替,比如 sed -i 's|/old/path|/new/path|g',省得满屏反斜杠。 - 永远先用不带
-i 的命令试一把,我见过同事把 nginx.conf 改没了,当场想拔服务器电源。
3. awk —— 切东西的(按列处理)
入门心法
很多人看到 {} 就头疼,其实你只需要记住:
基本切片
awk '{print $1, $3}' access.log # 打印第1列和第3列awk -F':''{print $1, $3}' /etc/passwd # 用冒号分隔
带条件过滤
awk '$9 >= 400 {print $1, $9}' access.log # 状态码>=400的awk -F':''$3 >= 1000 {print $1}' /etc/passwd # UID>=1000的用户awk '/error/ {print NR, $0}' app.log # 类似grep,还带行号
统计与聚合(SQL的GROUP BY)
# 统计每种状态码出现次数awk '{count[$9]++} END {for(code in count) print code, count[code]}' access.log# 统计每个IP的访问量awk '{ip[$1]++} END {for(i in ip) print i, ip[i]}' access.log | sort -rn -k2 | head
count[$9] 是个数组,每遇到一个状态码就加1,最后在 END 块里打印。
数学计算
# 计算响应时间的总和、平均、最大awk '{sum += $NF; if($NF>max) max=$NF} END {print "总时间:", sum, "平均:", sum/NR, "最大:", max}' access.log
$NF 是最后一列,不管它具体在第几列,反正就是最后一个字段。
多分隔符
# 日志格式:2023-10-01 10:23:45 [INFO] message# 用空格和方括号作为分隔符,提取级别awk -F'[ []+]''{print $4}' app.log
[ []+] 意思是匹配 [ 或 ] 或空格,多个连续分隔符会被当做一个。
格式化输出
awk -F':''{printf "%-20s %5d\n", $1, $3}' /etc/passwd
printf 可以对列对齐,适合生成报告。
踩坑提醒
- awk 的字段索引从 1 开始,
$0 是整行,别写成 $1 当整行使。 - 默认分隔符是空白(空格或tab),如果字段间有多个空格,awk 会自动压缩成一个,一般没问题。但如果想严格按单个字符分割,用
-F' ' 指定空格。 - 想用
$NF 但行内字段数不固定?NF 是字段总数,$NF 永远指向最后一个,安全。
4. 三剑客合体技(实战)
场景1:找出访问量TOP 5的IP
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -5
场景2:找出响应时间最慢的10个请求(假设最后一列是时间)
awk '$NF > 0 {print $NF, $0}' access.log | sort -nr | head -10
场景3:批量修改所有配置里的数据库连接串
grep -rl "db.example.com" /app/config/ | xargs sed -i 's/db.example.com/db.internal/g'
场景4:统计错误日志中各类错误码的数量
grep "ERROR" app.log | awk -F'ErrorCode=''{print $2}' | cut -d' ' -f1 | sort | uniq -c
场景5:从nginx日志里统计某个接口的平均响应时间
grep "/api/user/login" access.log | awk '{sum+=$NF} END {print sum/NR}'
场景6:实时监控日志,同时过滤和提取字段
tail -f app.log | awk '/ERROR/ {print $1, $NF}' | grep -v "health"
5. 一些大实话(坑与习惯)
grep
- 想用
+、|、() 这类正则,**要么转义,要么用 -E**。我习惯直接 grep -E,省心。 - 递归搜索时,默认会搜二进制文件,加
-I 跳过二进制,速度更快。 - 用
--color=auto 高亮匹配,很多发行版默认开了,没开就自己加个alias。
sed
- **永远先测试再
-i**:sed 's/old/new/g' file 看看输出对不对,没问题再 -i。 - 跨平台脚本注意:Linux 的 sed 和 macOS 的 sed 在
-i 上行为不同,最好用 sed -i.bak 统一备份。 - 正则里如果有
/,用其他分隔符(#、|)避免转义地狱。
awk
- 如果只想打印某几列,但列数很多,可以用
$1=$1; print 来重新格式化(触发OFS)。 BEGIN 块里可以设置分隔符、打印表头;END 块里做汇总。- 大文件用 awk 完全没问题,它是流式处理,不会一次性加载整个文件。
6. 快速记忆法
这三样东西,别看命令行长,用熟了就是肌肉记忆。不用刻意背,用的时候回来翻翻,多踩几次坑,就刻脑子里了。能解决问题的,就是好家伙。