这个命令是干啥的
awk 这个名字难记,我第一次看到还以为是什么缩写彩蛋。其实它来自三个人的姓氏首字母:Aho、Weinberger、Kernighan。你不需要记住这个,你只需要知道:awk 是 Linux 下处理结构化文本最爽的工具。
说白了,awk 就是一门微型脚本语言,专门用来处理"一行一行、一列一列"的数据。比如你有一个日志文件,每行用空格或逗号隔开,你想提取第2列和第5列,或者想算某一列的总和,或者想过滤出某个条件的数据,这些场景 awk 一把梭。
跟 grep 和 sed 放一起,awk 是"文本处理三剑客"里最能打的。grep 负责找,sed 负责替换,awk 负责提取和计算。
基本用法(3分钟上手)
awk 写起来很简洁:
# 打印每行的第1列和第3列
awk '{print $1, $3}' file.txt
解释一下:$1 是第1列,$2 是第2列,$0 是整行。
看个具体例子。假设有个文件 scores.txt:
# 姓名 科目 分数
张三 语文 85
李四 数学 92
王五 英语 78
# 只打印姓名和分数
awk '{print $1, $3}' scores.txt
输出:
张三 85
李四 92
王五 78
指定分隔符
默认 awk 用空格(或连续空格)当分隔符。如果你的文件是用逗号或者冒号隔开的,要用 -F:
# 以冒号为分隔符,打印第1和第5列
awk -F':' '{print $1, $5}' /etc/passwd
# 以逗号为分隔符,处理 CSV
awk -F',' '{print $2, $4}' data.csv
内置变量 NR、NF、FNR
这三个变量很常用:
- ·NR:当前行号(整个文件累计)
- ·NF:当前行的列数(有多少个字段)
- ·FNR:当前文件的行号(处理多个文件时分别计数)
# 打印每行的行号和列数
awk '{print "第" NR "行,共" NF "列"}' file.txt
# 只处理第2到第5行
awk 'NR>=2 && NR<=5 {print $0}' file.txt
进阶骚操作
条件过滤
awk 支持在动作前加条件,只有满足条件的行才会执行:
# 打印分数大于80的学生的姓名和分数
awk '$3 > 80 {print $1, $3}' scores.txt
# 打印分数在80到90之间(含)的
awk '$3 >= 80 && $3 <= 90 {print $0}' scores.txt
# 匹配包含"李"的行
awk '/李/ {print $0}' scores.txt
求和和统计
这是 awk 最实用的功能之一,累加计算:
# 计算所有学生分数的总和
awk '{sum += $3} END {print "总分:", sum}' scores.txt
# 计算平均分
awk '{sum += $3; count++} END {print "平均分:", sum/count}' scores.txt
这里的 END 关键字表示所有行处理完之后再执行后面的动作。对应地还有 BEGIN,在处理第一行之前执行:
# 打印带表头的统计
awk 'BEGIN {print "姓名\t分数"} {print $1 "\t" $3} END {print "---统计结束---"}' scores.txt
格式化输出
默认的 print 输出比较随意,想要对齐可以用 printf,用法和 C 语言一样:
# 左对齐,姓名占8个字符宽度,分数占3位
awk '{printf "%-8s %3d\n", $1, $3}' scores.txt
使用外部变量
有时候你需要在 awk 里用 shell 变量:
# 把 shell 变量传给 awk
min=80
awk -v threshold="$min" '$3 > threshold {print $0}' scores.txt
避坑指南
坑1:忘了 $ 符号
awk 里访问列必须加 $,但访问你自己定义的变量不能加:
# 错误!awk 会把 threshold 当成命令
awk '$3 > threshold {print $0}' scores.txt
# 正确
awk -v threshold=80 '$3 > threshold {print $0}' scores.txt
坑2:分隔符不是你想要的那个
如果文件里的分隔符是多个空格或者 tab,默认行为可能不对。明确指定:
# 明确用 tab 做分隔符
awk -F'\t' '{print $2}' file.tsv
坑3:处理大文件时变量不重置
如果你同时处理多个文件,NR 是全局累加的。想每个文件重新计数用 FNR:
# 每个文件分别列出1-3行
awk 'FNR <= 3 {print FILENAME": "$0}' file1.txt file2.txt
坑4:CSV 里有引号内的逗号
标准的 CSV 如果某字段被引号包着,里面的逗号不该当分隔符。原生 awk 处理不了这种情况。我的建议是用 csvkit 工具集或者 Python 的 csv 模块,别逞强用 awk。
实战场景(重点!结合真实运维场景)
场景1:分析 Nginx 访问日志
Nginx 的 access log 格式大致是这样(默认 combined 格式):
192.168.1.1 - - [22/Jun/2026:08:30:15 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/7.68"
我想统计今天所有返回 404 的请求及其请求路径:
# 提取状态码=404的行,打印IP、请求方法和路径
awk '$9 == 404 {print $1, $6, $7}' /var/log/nginx/access.log | head -20
我想找出访问量最大的前3个 IP:
# 统计每个IP出现的次数,按次数倒序排
awk '{ips[$1]++} END {for (ip in ips) print ips[ip], ip}' /var/log/nginx/access.log | sort -rn | head -3
这段代码我解释一下:ips[$1]++ 是 awk 的关联数组,以 IP 为 key 累加出现次数。END 块里遍历数组打印,然后用 sort 排序取 top 3。这个模式我几乎每周都用。
场景2:计算磁盘使用率
df -h 的输出要取出某个分区的使用率,直接 awk 搞定:
# 获取 /dev/vda1 的使用率(去掉%号)
df -h | grep '/dev/vda1' | awk '{print $5}' | tr -d '%'
或者一步到位:
# 直接提取第5列并去掉%号
df -h | awk '$1 ~ /vda/ {sub(/%/, "", $5); print $5}'
场景3:批量处理脚本中的配置项
我以前维护一个老项目,配置文件格式是 key=value,需要批量修改某些值。用 awk 一行搞定:
# 把 conf 文件中 TIMEOUT 行的值从 30 改成 60
awk -F'=' -v OFS='=' '$1 == "TIMEOUT" {$2 = 60} {print}' config.ini > config.ini.new
这里 OFS 是输出字段分隔符(Output Field Separator),设成等号保证输出格式不变。
今日作业
有一个文件 sales.txt,内容如下:
张三 1月 3000
李四 1月 4500
王五 1月 2200
张三 2月 3500
李四 2月 5000
王五 2月 2800
请写一条 awk 命令,完成以下需求:
1. 算出每个人的总销售额
2. 只打印总销售额超过 6000 的人
3. 输出格式:"姓名: 总销售额"
提示:用关联数组分人累加,END 块里过滤打印。