一、基本模型:模式 + 动作
awk '条件 { 动作 }' 文件
不写条件就是「每一行都执行」,不写动作就是「打印整行」。
awk '{print $1}' file # 打印第一列
awk '{print $1, $3}' file # 打印第 1、3 列(逗号会输出空格)
awk '{print NF}' file # 每行有多少列
awk '{print $NF}' file # 最后一列
$0 是整行,$1 是第一列,NF 是列数(所以 $NF 就是最后一列)。这是 awk 最核心的几个变量。
二、分隔符
默认按连续空白(空格或 Tab)分割,这比 cut -d' ' 强——多个空格不会切出空列。
awk -F: '{print $1}' /etc/passwd # 按冒号
awk -F',' '{print $2}' data.csv # 按逗号
awk -F'\t' '{print $3}' file # 按 Tab
-F 也支持正则,比如 -F'[;,|]' 同时按三种符号切。
三、条件过滤
awk '$3 > 100' file # 第 3 列大于 100
awk '$2 == "ERROR" {print $0}' app.log # 第 2 列等于 ERROR
awk '/Timeout/ {print $1, $NF}' app.log # 整行匹配正则
awk 'NR>=10 && NR<=20' file # 第 10–20 行
NR 是当前行号(跨文件累计),FNR 是当前文件内的行号,处理多个文件时用 FNR 更准。
四、统计:求和、计数、平均
# 求和
awk '{sum += $3} END {print sum}' file
# 计数
awk '/ERROR/ {n++} END {print n}' app.log
# 平均值(注意除零)
awk '{s += $3; n++} END {if (n) printf "%.2f\n", s/n}' file
END 块在所有行处理完后执行一次,是放汇总逻辑的地方。BEGIN 则在读取前执行,常用来打印表头或初始化变量。
日志耗时统计的实用写法:
awk -F'cost=' '{split($2, a, "ms"); s += a[1]} END {print s "ms"}' access.log
五、格式化输出
awk '{printf "%-20s %8.2f\n", $1, $3}' file
%-20s 左对齐 20 字符,%8.2f 右对齐 8 宽保留两位小数。做对齐的文本报表时比 echo 拼字符串方便。
六、四个能直接用的命令
# 1. 访问日志里耗时最长的 10 个请求
awk '{print $NF, $0}' access.log | sort -rn | head -10
# 2. 统计每个 IP 的请求数 Top10
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 3. 统计各状态码数量
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 4. 计算某列总和与平均
awk '{s+=$5; n++} END {printf "sum=%d avg=%.2f\n", s, s/n}' data.txt
这几个组合(sort | uniq -c | sort -rn)是日志分析的万能套路,值得记住。
七、注意点
- 字段不存在时是空字符串,参与数值运算按 0 处理,容易静默算错;必要时先判断
NF。 - CSV 有引号包裹的逗号时,
-F,会切错,这种情况应该用专门工具或 Python 的 csv 模块。 - 大文件:awk 是流式的,几 GB 也没问题,但
sort会占内存,必要时加-S限制。 - 中文字符:awk 按字节处理,
length()在非 UTF-8 locale 下可能算错,设置LC_ALL=C.UTF-8。
小结
$1 取列、$NF 取最后一列、-F 指定分隔符、NR 是行号、END 做汇总。配合 sort | uniq -c | sort -rn 能解决大部分日志统计需求。