awk 是命令行里做「按列处理」最顺手的工具,但它的语法和普通编程语言差别不小:变量不用声明、没初始化就有值、整个程序包在单引号里、$1 和 $0 含义完全不同。下面按使用频率从高到低,把最容易踩的几类讲清楚。

一、字段分隔:-F 与 FS 的三种写法

默认情况下 awk 按「连续空白」切分,所以 ps aux 这种多空格输出不用额外处理:

ps aux | awk '{print $2, $11}'

指定分隔符有三类写法,但生效时机不同:

awk -F',' '{print $2}' data.csv
awk -F, '{print $2}' data.csv
awk 'BEGIN{FS=","} {print $2}' data.csv

-F 和 BEGIN{FS=...} 在处理第一行之前就设置好,对所有行生效;把 FS="," 写在普通代码块里,第一行已经按默认空白切完了,只有第二行起才生效。所以三种写法并不等价:只有前两种能保证第一行也按新分隔符切分。需要改分隔符,一定放 -F 或 BEGIN 里。

踩坑:OFS 不会自动生效

awk 'BEGIN{OFS=","} {print $1 $2}' file.txt

$1 $2 是字符串拼接,OFS 不参与,结果是两段粘在一起。要么用逗号写 print $1, $2(这时 OFS 才生效),要么显式写 print $1 "," $2。

想把空白分隔的文件转成 CSV,需要先重建记录:

awk 'BEGIN{OFS=","} {$1=$1; print}' file.txt

$1=$1 看起来什么都没做,实际是给某个字段赋值,awk 就会用 OFS 重新拼接整行。

二、条件判断:字符串比较和数值比较不是一回事

awk '$3 > 100' data.txt

这行的含义是「第 3 列大于 100 就输出整行」。awk 会根据内容自动判断类型:看起来像数字就按数值比,否则按字符串比。问题是 $3 里混了单位,比如 100MB,就会退化成字符串比较,结果完全不对。

需要明确数值转换时,用 +0 强制:

awk '$3+0 > 100' data.txt

NF 是当前行的字段数,$NF 是最后一列,$(NF-1) 是倒数第二列。日志格式不稳定时,用 $NF 取最后一段比数「第几列」可靠。

三、数组:所有数组都是关联数组

awk 没有普通数组,a[1] 和 a["key"] 是同一个东西。下标统一按字符串处理,a[1] 与 a["1"] 指向同一元素,数字键只是写法上的便利。好处是分组统计一行就能写完:

awk '{c[$1]++} END{for(k in c) print k, c[k]}' data.txt

两个必知点:

  1. 未初始化的变量参与数值运算时按 0,参与字符串拼接时按空串。所以 sum+=$2 不需要先声明。
  2. for (k in c) 的顺序是随机的,不是插入顺序。要固定顺序,得自己排序,或改用 gawk 的 asort(mawk 不支持)。

清理数组用 delete c,删除单个元素用 delete c[key]。

四、内置变量速记

变量 含义
NR 已读记录数(跨文件累计)
FNR 当前文件内的记录数(多文件时用它区分文件边界)
NF 当前行的字段数
$0 整行内容
FS / OFS 输入 / 输出字段分隔符
RS / ORS 输入 / 输出记录分隔符(默认换行)

NR>1 跳过表头、END{print NR} 统计总行数靠 NR;处理多个文件时用 FNR==1 判断每个文件的第一行,例如跳过每个文件的表头:awk 'FNR>1' a.csv b.csv。

五、实战五例

1. 求某列总和

awk '{sum+=$2} END{print sum}' data.txt

2. 分组求和

awk -F, '{a[$1]+=$2} END{for(k in a) print k, a[k]}' data.csv

3. 行去重且保持原顺序

awk '!seen[$0]++' file.txt

seen[$0]++ 先取值再自增,第一次遇到时值是 0(假),取反后为真,之后都为假,所以只有第一次出现会被打印。

4. 按表头名取列,避免列顺序变化

awk -F, 'NR==1{for(i=1;i<=NF;i++) h[$i]=i; next} {print $h["name"]}' data.csv

5. 找出字段数不对的脏数据行

awk -F, 'NF!=3 {print NR": "$0}' data.csv

六、跨平台的坑

  • macOS 自带 BSD awk,IGNORECASE、asort、gensub 这些 gawk 扩展不可用,脚本换机器前先确认
  • 程序用单引号包住时,内部不能再直接写单引号,需要 '"'"' 这样拼接(关引号 + 双引号包住的单引号 + 开引号)
  • $ 写在双引号里会被 shell 先展开,所以 awk 程序一律用单引号

更多常用写法见 awk 速查,系统侧命令可参考 Linux 命令速查,正则调试用 正则测试。