awk —— 按列统计的文本处理语言

运维工具手册 04-文本处理 5.5 KB 原文

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. 常见坑

  1. 默认按任意空白切分,多空格会被合并;要按单空格切用 -F' '。
  2. 字符串比较 vs 数值比较:$1 > 100 若 $1 是字符串会按字典序,强制转换用 $1+0。
  3. $0 修改后 $1 不会自动重算(除非用 gsub 等会重建的),需重新 split。
  4. print > file 只打开一次,多次 > 不会覆盖,这与其他语言不同。
  5. NR 与 FNR 混用:多文件时 NR 累加,判断"每个文件第一行"要用 FNR==1。
  6. -F'\t' 在部分 shell 不生效:写 -F $'\t'(bash)或 -F'\t' 加引号。
  7. gawk 与 mawk 差异:Debian 默认 mawk,不支持 gensub、ARGIND、asort。
  8. 中文字符长度:length() 按字节还是字符取决于 locale,中文环境要 LC_ALL=C.UTF-8。
  9. 浮点精度:金额计算别用 awk,会丢精度,用 bc 或 Python。
  10. END 里访问 $0:是最后一行的内容,容易误用。
  11. 引号嵌套:shell 单引号内不能再用单引号,脚本复杂时写成文件 awk -f script.awk。
  12. 大文件性能: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                  # 行数