Appearance
12|awk 字段处理
grep 找行,sed 改行。但很多时候要按字段处理——统计日志里每个状态码出现多少次、求某列数字的和、按某列的值分组。这就是 awk 的强项。
本篇讲 awk 的基本模型、字段访问、内置变量、模式动作和统计聚合。
一、awk 的处理模型
awk 逐行读输入,每行按分隔符拆成字段,对每行执行一段动作。基本格式:
bash
awk '模式 {动作}' 文件模式匹配的行才执行动作,不写模式就对所有行执行。最简单的:
bash
echo "a b c" | awk '{print $2}'
# b$1 $2 $3 是第 1、2、3 个字段,$0 是整行。默认分隔符是空格或 tab。
二、指定分隔符
处理日志时常以特定字符分隔。-F 指定分隔符:
bash
# 以冒号分隔(如 /etc/passwd)
awk -F: '{print $1, $7}' /etc/passwd
# root /bin/bash
# daemon /sbin/nologin
# 以逗号分隔(CSV)
awk -F, '{print $1}' data.csv也可以在程序里设 FS:
bash
awk 'BEGIN{FS=":"} {print $1}' /etc/passwdBEGIN 在处理输入前执行,常用来设变量。END 在处理完所有行后执行。
三、内置变量
| 变量 | 含义 |
|---|---|
$0 | 整行 |
$1 $2 ... | 各字段 |
NF | 当前行的字段数 |
NR | 当前行号(累计) |
FNR | 当前文件内的行号 |
FS | 字段分隔符(输入) |
OFS | 输出分隔符 |
RS | 行分隔符 |
$NF 是最后一个字段(NF 是字段数,$NF 取该数字对应的字段):
bash
echo "a b c d" | awk '{print $NF}'
# dNR 做行号:
bash
awk '{print NR": "$0}' file
# 1: 第一行
# 2: 第二行四、模式匹配
只处理匹配的行:
bash
# 只处理含 error 的行
awk '/error/ {print}' app.log
# 只处理第 2 列大于 100 的行
awk '$2 > 100 {print $1, $2}' data.txt模式可以是正则(/.../)、比较表达式($2 > 100)、或组合。
五、统计聚合
awk 最强的能力——用关联数组做统计。
统计每个状态码出现次数(Nginx access log 第 9 列是状态码):
bash
awk '{count[$9]++} END {for (k in count) print k, count[k]}' access.log
# 200 5000
# 404 120
# 500 3count[$9]++ 用第 9 列的值做数组下标累加。END 块遍历数组输出结果。
求某列的和:
bash
awk '{sum += $5} END {print sum}' data.txt找访问最多的 IP(access log 第 1 列是 IP):
bash
awk '{count[$1]++} END {for (k in count) print count[k], k}' access.log | sort -rn | head -10按字段分组求和:
bash
# 每个服务的总请求数(服务在第 1 列,数量在第 3 列)
awk '{sum[$1] += $3} END {for (k in sum) print k, sum[k]}' data.txt六、BEGIN 和 END
BEGIN 处理前执行,END 处理后执行。常用模式:
bash
awk '
BEGIN { print "开始统计"; FS=","; total=0 }
$3 > 100 { total += $3; print $1, $3 }
END { print "合计:", total }
' data.csvBEGIN 里设分隔符、初始化变量、打印表头;END 里打印汇总结果。
七、格式化输出
printf 比 print 更可控,输出对齐的表格:
bash
awk '
BEGIN { printf "%-20s %10s\n", "IP", "次数" }
{ count[$1]++ }
END { for (k in count) printf "%-20s %10d\n", k, count[k] }
' access.log | sort -k2 -rn | headtext
IP 次数
10.0.0.5 1234
10.0.0.8 987八、条件判断和循环
awk 里有完整的 if 和循环:
bash
awk '{
if ($9 >= 500) {
errors++
} else if ($9 >= 400) {
warns++
} else {
ok++
}
} END {
print "正常:", ok, "告警:", warns, "错误:", errors
}' access.logawk 是一门完整的编程语言,能写复杂逻辑。但太复杂的逻辑建议用 Python,awk 的强项是一两行能搞定的文本处理和统计。
九、实战例子
统计 Nginx 访问日志,输出前 10 个被请求最多的 URL:
bash
awk '{url[$7]++} END {for (k in url) print url[k], k}' access.log | sort -rn | head -10$7 是请求的 URL 路径。
找 5xx 错误最多的接口:
bash
awk '$9 ~ /^5/ {err[$7]++} END {for (k in err) print err[k], k}' access.log | sort -rn$9 ~ /^5/ 匹配状态码以 5 开头的行。
计算平均响应时间(假设第 11 列是 $upstream_response_time):
bash
awk '{sum += $11; n++} END {printf "平均: %.3f 秒\n", sum/n}' access.log十、awk 还是 grep+sort+uniq
统计去重,命令组合也能做:
bash
# 统计 IP 出现次数
awk '{count[$1]++} END {for (k in count) print count[k], k}' access.log | sort -rn | head
# 等价的命令组合
cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head命令组合对简单去重够用,但 awk 在"按某列分组对另一列求和""带条件过滤的统计"上更灵活。
学会了之后
awk 是文本统计的主力。$1 访问字段、NF/NR 管字段数行号、-F 设分隔符、关联数组 {count[$x]++} 做统计、BEGIN/END 做初始化和汇总。运维里大量的"统计日志某字段"用 awk 一行搞定。
下一篇讲重定向进阶——进程替换和脚本里的组合用法。