awk 的名字取自其三位创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏首字母。它虽然常被称为“命令行文本处理工具”,但本质上是一门专为处理结构化文本而设计的解释型编程语言。当需要从日志、CSV、配置文件等文本中提取字段、进行计算、格式化报表或做条件过滤时,awk 往往比 sed 和 grep 更加直接和强大。
1. awk 的工作模型
awk 遵循“记录-字段”模型处理输入:
- 默认每条记录就是一行,每读取一行就自动将其按分隔符拆分为若干字段。
- 默认的字段分隔符是任意连续空白(空格或 Tab),你也可以通过
-F参数指定分隔符。 - 字段按顺序用
$1、$2…$N表示,整个当前行用$0代表。
awk 程序的结构通常是:
条件 { 动作 }
它会逐行读取输入,对满足“条件”的行执行大括号中的“动作”。缺少“条件”时,则默认对每一行都执行动作;缺少“动作”时,则默认打印整行。
2. 基本语法速查(实用片段)
先看几个最常用的单行命令,感受 awk 的高效:
- 打印第1列和第3列
awk '{print $1, $3}' file.txt
输出文件每行的第1和第3个字段,用空格隔开。
- 指定分隔符
对于逗号分隔的 CSV 文件:
awk -F',' '{print $2}' data.csv
冒号分隔的 /etc/passwd:
awk -F: '{print $1, $7}' /etc/passwd # 用户名和登录 shell
- 按条件过滤
只处理第3列大于 100 的行:
awk '$3 > 100' file.txt
只打印第一列等于 "ERROR" 的行:
awk '$1 == "ERROR" {print $0}' app.log
- 使用正则匹配
打印包含 “fail” 的行(不区分大小写可借助字符类 [Ff]ail 或 tolower 函数):
awk '/fail/' logfile
- 统计与计算
累加第2列并打印总和:
awk '{sum += $2} END {print sum}' data.txt
统计行数(即 wc -l 的作用):
awk 'END {print NR}' file.txt
3. 实用场景拆解
下面通过几个典型需求,展示 awk 如何让文本处理变得轻松。
场景一:日志字段提取
Nginx 访问日志通常包含 IP、时间、请求路径、状态码等,用空格分隔。想要输出每个 IP 地址和对应的状态码:
awk '{print $1, $9}' nginx-access.log
如果想只查看 404 错误的 IP:
awk '$9 == 404 {print $1}' nginx-access.log
场景二:计算磁盘使用率df -h 可能输出带百分比符号的字段,不利于直接运算。用 df -P 结合 awk 可以精确计算使用率:
df -P | awk 'NR>1 {print $1, $5}' # 打印文件系统及其使用百分比
其中 NR>1 表示跳过第一行标题。
场景三:多文件合并与定制报表
awk 可以同时处理多个文件,内置变量 FILENAME 能标识当前文件名。例如,统计多个日志文件的错误行总数:
awk '/ERROR/ {count++} END {print "Total errors:", count}' /var/log/*.log
场景四:简单格式化输出
awk 内置 printf 函数,可以像 C 语言一样排版。以下命令将文件和大小以整齐的列输出(ls -l 的替代):
ls -l | awk '{printf "%-30s %10s bytes\n", $9, $5}'
4. BEGIN 与 END 块
awk 允许在读取任何数据之前执行 BEGIN 块(如打印表头),在所有输入处理完成后执行 END 块(如打印汇总)。这是生成简单报表的标准模式:
awk 'BEGIN {print "Name\tScore"}
{print $1 "\t" $2}
END {print "---end---"}' scores.txt
5. 内建变量速记(常用)
NR:已读取的总记录数(行数),在多文件处理时依次累加。FNR:当前文件的记录数,每个文件重新计数。NF:当前行的字段数。$NF就是最后一个字段,$(NF-1)是倒数第二个。FS:输入字段分隔符,等效于-F选项。OFS:输出字段分隔符,默认为空格,可在BEGIN中重设。RS:输入记录分隔符,默认为换行符;设为空字符串时以空行分隔。ORS:输出记录分隔符。
例如,把逗号分隔的 CSV 转为冒号分隔:
awk 'BEGIN {FS=","; OFS=":"} {print $1, $2, $3}' data.csv
6. 关联数组与去重
awk 原生支持关联数组(即字典),非常适合做去重和计数。
统计日志中每个 IP 的出现次数:
awk '{ip[$1]++} END {for (i in ip) print i, ip[i]}' access.log
按值降序输出(需要借助管道或 GNU awk 的 asorti 等):
awk '{ip[$1]++} END {for (i in ip) print ip[i], i}' access.log | sort -rn
7. 何时使用 awk
当数据按行列规则排列,且处理逻辑涉及字段级别的判断、运算或格式化时,awk 是最佳选择:
- 比 sed 更适合处理按字段组织的文本。
- 比 grep 多了计算和格式化能力。
- 比写一个完整的 Python 脚本更轻量快捷,尤其适合在管道中作为过滤器。
掌握了 awk,你就拥有了一把在文本流中随时按需切片、统计、变换的“瑞士军刀”。它不会取代通用编程语言,但在命令行下处理结构化文本的效率,是任何其他工具难以匹敌的。