Skip to content

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/passwd

BEGIN 在处理输入前执行,常用来设变量。END 在处理完所有行后执行。

三、内置变量

变量含义
$0整行
$1 $2 ...各字段
NF当前行的字段数
NR当前行号(累计)
FNR当前文件内的行号
FS字段分隔符(输入)
OFS输出分隔符
RS行分隔符

$NF 是最后一个字段(NF 是字段数,$NF 取该数字对应的字段):

bash
echo "a b c d" | awk '{print $NF}'
# d

NR 做行号:

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 3

count[$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.csv

BEGIN 里设分隔符、初始化变量、打印表头;END 里打印汇总结果。

七、格式化输出

printfprint 更可控,输出对齐的表格:

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 | head
text
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.log

awk 是一门完整的编程语言,能写复杂逻辑。但太复杂的逻辑建议用 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 一行搞定。

下一篇讲重定向进阶——进程替换和脚本里的组合用法。