awk 是 Linux 下最强大的文本处理工具之一,名字取自三位创始人 Aho、Weinberger、Kernighan 的姓氏首字母。很多人只用它做简单的列提取,其实 awk 的能力远不止于此。
awk 的核心模型
awk 的工作流程可以概括为:
awk 'pattern { action }' file
- pattern:匹配条件(正则、表达式、范围)
- action:执行的操作(打印、计算、变量赋值)
对于每一行,awk 会:
- 自动按分隔符分割字段(默认空格)
- 字段存入
$1, $2, $3...,整行是$0 - 检查 pattern,匹配则执行 action
bash
# 提取第一列和第三列
awk '{ print $1, $3 }' data.txt
# 只处理包含 "error" 的行
awk '/error/ { print $0 }' app.log
# 计算文件总行数
awk 'END { print NR }' data.txt
NR 是内置变量,表示当前行号(Number of Records)。END 是特殊模式,在所有行处理完后执行。
字段分隔符:不只是空格
-F 参数指定字段分隔符:
bash
# CSV 文件按逗号分割
awk -F',' '{ print $1, $3 }' data.csv
# 使用正则表达式:一个或多个空格
awk -F'[ ]+' '{ print $1 }' data.txt
# 多字符分隔符
awk -F'|' '{ print $1 }' data.txt
也可以在脚本内设置 FS(Field Separator):
bash
awk 'BEGIN { FS = "," } { print $1, $3 }' data.csv
BEGIN 在处理任何行之前执行,常用于初始化变量。
内置变量的秘密
awk 提供了多个内置变量:
| 变量 | 含义 |
|---|---|
$0 |
整行内容 |
$1~$n |
第 n 个字段 |
NF |
当前行字段数(Number of Fields) |
NR |
当前行号(全局) |
FNR |
当前行号(当前文件) |
FS |
字段分隔符 |
OFS |
输出字段分隔符 |
RS |
行分隔符 |
ORS |
输出行分隔符 |
NF 的妙用:引用最后一个字段
bash
# 打印每行的最后一个字段
awk '{ print $NF }' data.txt
# 打印倒数第二个字段
awk '{ print $(NF-1) }' data.txt
条件判断与循环
awk 支持 if-else 和 for/while 循环:
bash
# 按条件过滤并标记
awk '{
if ($3 > 100) {
print $1, "HIGH"
} else {
print $1, "NORMAL"
}
}' data.txt
# 计算每行的字段和
awk '{
sum = 0
for (i = 1; i <= NF; i++) {
sum += $i
}
print sum
}' numbers.txt
数组与统计
awk 的数组是关联数组(associative array),键可以是任意字符串:
bash
# 统计每个单词出现次数
awk '{
for (i = 1; i <= NF; i++) {
count[$i]++
}
}
END {
for (word in count) {
print word, count[word]
}
}' text.txt
# 按访问量统计 HTTP 状态码
awk '{ count[$9]++ } END { for (code in count) print code, count[code] }' access.log
这段代码中 $9 是 Nginx 日志的状态码字段(假设标准格式)。
实战案例:分析 Nginx 访问日志
假设日志格式:
192.168.1.1 - - [10/May/2026:10:30:45 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
1. 统计 Top 10 访问 IP
bash
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10
纯 awk 实现:
bash
awk '{
ip[$1]++
}
END {
for (i in ip) print ip[i], i
}' access.log | sort -rn | head -10
2. 计算平均响应时间
假设日志格式包含响应时间(最后一个字段):
bash
awk '{
total += $NF
count++
}
END {
print "Average:", total/count, "ms"
}' access.log
3. 提取 4xx 和 5xx 错误
bash
# 提取所有 4xx 和 5xx 状态码的请求
awk '$9 ~ /^[45][0-9][0-9]$/ { print $0 }' access.log
# 统计错误类型分布
awk '$9 ~ /^[45][0-9][0-9]$/ {
errors[$9]++
}
END {
for (code in errors) print code, errors[code]
}' access.log
~ 是正则匹配操作符,$9 ~ /^.../ 表示第 9 个字段匹配正则。
性能优化技巧
1. 跳过无效行
用 next 跳过不需要处理的行:
bash
awk '/^#/ { next } { print $1 }' config.conf
跳过注释行(以 # 开头)。
2. 只处理前 N 行
bash
awk 'NR > 100 { exit } { print $1 }' data.txt
处理前 100 行后退出,避免读取整个大文件。
3. 多文件处理时的 FNR
当处理多个文件时,NR 是全局行号,FNR 是当前文件行号:
bash
# 每个文件单独统计
awk 'FNR == 1 { print "File:", FILENAME } { print NR, FNR, $0 }' file1.txt file2.txt
复杂案例:计算移动平均
假设有一个温度数据文件,每行一个温度值,计算 3 点移动平均:
bash
awk '{
values[NR] = $1
if (NR >= 3) {
sum = values[NR] + values[NR-1] + values[NR-2]
print (NR-2), sum/3
}
}' temperature.txt
awk vs sed vs grep
很多人分不清这三个工具的边界:
| 工具 | 核心能力 | 典型场景 |
|---|---|---|
| grep | 行过滤 | 快速搜索匹配行 |
| sed | 流编辑 | 替换、删除、插入 |
| awk | 字段处理 + 计算 | 统计、报表、格式化 |
三者常组合使用:
bash
# 组合示例:提取 error 行,替换时间戳格式,统计按小时分布
grep "ERROR" app.log | \
sed 's/\[.*\]//' | \
awk '{ count[$1]++ } END { for (h in count) print h, count[h] }'
小结
awk 的强大在于:
- 自动字段分割,省去手动 split
- 完整的编程语言(变量、数组、函数、循环)
- 内置的模式匹配机制
掌握 awk,处理文本文件就像用 SQL 查询数据库一样高效。复杂的统计、格式化、转换任务,一行 awk 命令就能搞定。
相关工具:Linux sed 命令 | 文本去重工具 | Grep 命令详解