awk —— 按列统计的文本处理语言
awk —— 按列统计的文本处理语言
分类:04 文本处理 适用:全平台(GNU awk / mawk / BSD awk;
gawk功能最全) 一句话:日志按字段求和、分组计数、格式化输出,一行命令顶一个小脚本。
1. 它解决什么问题
- 日志里按接口统计请求数、平均耗时、最大耗时。
- CSV/TSV 按列求和、过滤、重排。
- 从命令输出里精确取某个字段。
- 按某个 key 聚合(GROUP BY 的终端版)。
2. 模型
awk 'pattern { action }' file
- 逐行读入,自动按
FS(默认空白)切分为$1 $2 ...,$0是整行。 pattern省略表示每行都执行;action省略表示打印整行。
awk '{print $1, $3}' file # 打印第 1、3 列
awk '{print $NF}' file # 最后一列
awk '{print $(NF-1)}' file # 倒数第二列
awk 'NR==1' file # 第一行
awk 'NR>=10 && NR<=20' file # 10-20 行
awk '/error/ {print $0}' file # 匹配行
awk '!/debug/' file # 排除
awk 'END {print NR}' file # 总行数
3. 分隔符
awk -F: '{print $1}' /etc/passwd # 冒号分隔
awk -F'\t' '{print $2}' data.tsv # Tab
awk -F'[,;]' '{print $2}' data.csv # 多分隔符(正则)
awk -F'[[:space:]]+' '{print $3}' file
awk -v FS=',' -v OFS='|' '{print $1,$2}' d.csv # 输入逗号,输出竖线
OFS 决定 print 多字段之间的分隔符(默认空格)。
4. 内建变量
| 变量 | 含义 |
|---|---|
$0 |
整行 |
$1..$n |
第 n 列 |
NF |
当前行字段数 |
NR |
当前行号(跨文件累计) |
FNR |
当前文件内行号 |
FS |
输入分隔符 |
OFS |
输出分隔符 |
ORS |
输出记录分隔符(默认换行) |
RS |
输入记录分隔符 |
FILENAME |
当前文件名 |
ARGIND |
当前文件序号(gawk) |
5. 计算与统计
# 求和
awk '{s+=$2} END {print s}' nums.txt
# 平均
awk '{s+=$2; n++} END {printf "avg=%.2f\n", s/n}' nums.txt
# 最大/最小
awk 'NR==1{m=$2} $2>m{m=$2} END{print m}' nums.txt
# 条件求和
awk '$3=="ERROR" {c++} END {print c}' app.log
# 分组计数(最常用)
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head
# 或纯 awk(不排序)
awk '{c[$1]++} END {for (k in c) print c[k], k}' access.log
# 分组求和
awk '{sum[$1]+=$3} END {for (k in sum) printf "%s\t%.0f\n", k, sum[k]}' data.txt
# 二维聚合
awk '{c[$1","$2]++} END {for (k in c) print k, c[k]}' data.txt
printf 格式常用:%s 字符串、%d 整数、%.2f 两位小数、%10s 右对齐、%-10s 左对齐、\t 制表符。
6. 条件与逻辑
awk '$3 > 100' file # 数值比较
awk '$1 ~ /^10\./' file # 字段匹配正则
awk '$1 !~ /health/' file # 不匹配
awk 'NF > 5' file # 字段数大于 5
awk '$2 == "GET" && $9 == 500' access.log # 与
awk '$9 >= 500 && $9 < 600' access.log # 5xx
awk 'length($0) > 200' file # 行长
7. 字符串函数
awk '{print length($0)}' file # 长度
awk '{print substr($1, 1, 4)}' file # 截取
awk '{print toupper($1)}' file
awk '{print tolower($1)}' file
awk '{gsub(/foo/, "bar"); print}' file # 全局替换(原地改 $0)
awk '{sub(/^ +/, ""); print}' file # 去行首空格
awk '{n=split($0, a, ","); print n, a[2]}' file # 切分为数组
awk '{print index($0, "error")}' file # 子串位置
awk '{print sprintf("%05d", $1)}' file # 格式化
awk '{print $1 > "/tmp/out.txt"}' file # 重定向输出
8. 实战场景
8.1 Nginx 日志:按接口统计 QPS 与耗时
# 假设格式:$remote_addr - - [$time_local] "$request" $status $body_bytes_sent $request_time
# 状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -nr
# 5xx 的 URL TOP10
awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -nr | head -10
# 每个 URL 的平均/最大耗时
awk '{u=$7; t=$NF; s[u]+=t; c[u]++; if(t>m[u]) m[u]=t}
END {for (k in s) printf "%-40s avg=%.3f max=%.3f n=%d\n", k, s[k]/c[k], m[k], c[k]}' \
access.log | sort -t= -k3 -nr | head -20
8.2 按分钟统计请求量(找尖峰)
awk '{print substr($4, 2, 17)}' access.log | sort | uniq -c | sort -nr | head
# $4 形如 [05/Nov/2026:14:32:01,substr 取到分钟
8.3 CSV 按列求和与过滤
awk -F, 'NR>1 {sum+=$4} END {printf "total=%.2f\n", sum}' sales.csv
awk -F, 'NR==1 || $3=="华东"' sales.csv > east.csv
awk -F, -v OFS=',' 'NR>1 {print $1, $3, $4*1.1}' sales.csv
CSV 字段含逗号/引号时
awk -F,会错位,用 Python 的csv或mlr。
8.4 处理多文件并标记来源
awk '{print FILENAME, FNR, $0}' *.log | head
8.5 按 key 合并两个文件(join 的替代)
# 文件1: id name 文件2: id amount
awk 'NR==FNR {name[$1]=$2; next} {print $1, name[$1], $2}' f1.txt f2.txt
NR==FNR 是 awk 处理多文件的经典技巧:第一个文件读入数组,第二个文件做匹配。
8.6 取进程内存 TOP
ps -eo rss,comm --no-headers | awk '{s[$2]+=$1} END {for (k in s) printf "%.1fM\t%s\n", s[k]/1024, k}' | sort -nr | head
9. 常见坑
- 默认按任意空白切分,多空格会被合并;要按单空格切用
-F' '。 - 字符串比较 vs 数值比较:
$1 > 100若$1是字符串会按字典序,强制转换用$1+0。 $0修改后$1不会自动重算(除非用gsub等会重建的),需重新split。print > file只打开一次,多次>不会覆盖,这与其他语言不同。NR与FNR混用:多文件时NR累加,判断"每个文件第一行"要用FNR==1。-F'\t'在部分 shell 不生效:写-F $'\t'(bash)或-F'\t'加引号。gawk与mawk差异:Debian 默认mawk,不支持gensub、ARGIND、asort。- 中文字符长度:
length()按字节还是字符取决于 locale,中文环境要LC_ALL=C.UTF-8。 - 浮点精度:金额计算别用 awk,会丢精度,用
bc或 Python。 END里访问$0:是最后一行的内容,容易误用。- 引号嵌套:shell 单引号内不能再用单引号,脚本复杂时写成文件
awk -f script.awk。 - 大文件性能:awk 很快,但数组聚合会吃内存,千万级唯一 key 要评估。
10. 速查表
awk '{print $1,$3}' f # 取列
awk -F: '{print $1}' /etc/passwd # 指定分隔符
awk 'NR==1' f # 首行
awk 'NR>1' f # 去表头
awk '$3>100' f # 数值过滤
awk '/err/' f # 正则匹配
awk '{s+=$2} END{print s}' f # 求和
awk '{s+=$2;n++} END{printf "%.2f\n",s/n}' f # 平均
awk '{c[$1]++} END{for(k in c) print c[k],k}' f # 分组计数
awk '{a[$1]+=$2} END{for(k in a) print k,a[k]}' f # 分组求和
awk 'NR==FNR{a[$1]=$2;next}{print $1,a[$1],$2}' f1 f2 # 关联
awk -F, 'NR>1{print $1,$3}' d.csv # CSV
awk '{gsub(/x/,"y");print}' f # 替换
awk 'END{print NR}' f # 行数