awk本节摘要:
awk是本章三巨头(grep/sed/awk)里最难、也最强大的一个。它和前两者的根本区别在于:grep和sed的视角是「按行处理字符串」,而awk的视角是「按行、按列处理结构化数据」。它本质上是一个「专为表格数据设计的迷你编程语言」——你写一段awk脚本,它对输入的每一行执行这段脚本。本节要把awk的心智模型讲透:它的「记录/字段」模型(一行是一条记录,按分隔符切成字段$1/$2),它的「模式-动作」结构(满足某模式的行才执行某动作),以及它的三大场景(打印列、筛选并计算、分组统计)。很多人对awk望而生畏,是因为没建立这个心智模型,把它当grep那样记选项——一旦你理解了「awk是一个小型编程语言」,它就从玄学变成了利器。
内容来源:原项目「Linux 命令大全」
command/awk.md,精选并套用体系化模板。
阅读完本节,你应当能够:
awk 的工作模型:把输入按行切成「记录」,每行按分隔符切成「字段」($1/$2/.../$0 整行),对每行执行「模式-动作」。{print $N} 提取指定列,用 -F 指定字段分隔符。awk '$3 > 100 {print $1}'),理解「模式为真才执行动作」。BEGIN/END 块做初始化与收尾统计(如求总和、平均、计数)。awk 与 grep/sed 的适用场景:结构化按列数据用 awk,纯文本过滤用 grep。awk 的心智模型理解 awk 的关键,是理解它的三个心智模型。
awk 把输入看成一张表:每一行是一条「记录」(record),每一行内部按分隔符切成若干「字段」(field)。字段用 $1、$2、$3 访问,$0 表示整行。
假设输入是(ls -l 的输出): -rw-r--r-- 1 root root 4096 Aug 4 file.txt 字段编号: $1 $2 $3 $4 $5 $6 $7 整行即 $0
默认分隔符是「连续的空格或制表符」。如果数据是 CSV(逗号分隔),用 -F',';如果是冒号分隔(/etc/passwd),用 -F':'。
awk '{print $1, $3}' file # 打印第 1、3 字段 awk -F':' '{print $1}' /etc/passwd # 冒号分隔, 打印用户名(第 1 列) awk -F',' '{print $2}' data.csv # 逗号分隔, 打印第 2 列
awk 的语法骨架是 模式 { 动作 }。只有当模式为真(即该行满足条件)时,才执行动作。模式可以省略(默认对所有行执行动作),动作也可以省略(默认打印整行)。
awk '{print $1}' file # 无模式: 对所有行打印第 1 列 awk '$3 > 100 {print $1}' file # 有模式: 只对"第3列>100"的行打印第1列 awk '/error/ {print $0}' log # 模式是正则: 含 error 的行打印整行(等价 grep) awk '$3 > 100' file # 无动作: 默认打印整行(满足模式的行)
💡 技巧:看
awk脚本时,先找{ },它前面的是「模式」(条件),里面的是「动作」(干啥)。这个二分法能帮你读懂任何awk命令。
awk 程序的执行顺序是:先执行一次 BEGIN 块,再对每一行执行主块,最后执行一次 END 块。
awk 'BEGIN{print "开始"} {print $1} END{print "结束, 共" NR "行"}' file # └ 程序开始时一次 └ 对每行 └──── 所有行处理完一次
BEGIN 常用于打印表头、初始化变量;END 常用于打印汇总(总和、平均、计数)。两个内置变量最常用:NR(已读记录数,即行号)、NF(当前行的字段数,$NF 即最后一列)。
ls -l | awk '{print $1, $9}' # ls -l 输出里取"权限"和"文件名" awk -F':' '{print $1, $7}' /etc/passwd # 用户名 + 默认 shell awk '{print $NF}' file # 打印每行最后一个字段($NF) awk '{print $(NF-1)}' file # 打印倒数第二个字段
ps aux | awk '$3 > 10 {print $2, $3, $11}' # CPU 占用>10% 的进程: 打印 PID/CPU%/命令 awk -F',' '$2 > 1000 {print $1}' sales.csv # 销售额>1000 的记录名 awk 'NR==3 {print $0}' file # 只打印第 3 行 awk 'NR>=10 && NR<=20 {print $0}' file # 打印第 10-20 行
awk 能在多行之间累积变量,这是它超越 grep/sed 的关键能力——它能「算」。
# 求某列总和 awk '{sum += $1} END {print sum}' nums.txt # 累加每行第 1 列, 最后打印总和 # 统计文件行数(等价 wc -l, 但能配合条件) awk 'END {print NR}' file # 打印总行数 # 按某列分组求和(经典: 按部门统计销售额) # sales.csv 格式: 部门,金额 awk -F',' '{sum[$1] += $2} END {for (k in sum) print k, sum[k]}' sales.csv # 解释: sum 是关联数组, key 是部门, value 是累计金额 # 每行把金额加到对应部门; END 时遍历数组打印
最后这个「按某列分组求和」是 awk 最强大的用法——它用「关联数组」(类似 Python 的 dict)实现了 SQL GROUP BY 的效果,而且只用一行。这是 awk 把命令行从「查找」升级到「分析」的关键。
# 找出日志里每个错误码出现的次数, 按次数降序(完整管道) grep -oE 'code=[0-9]+' app.log | awk -F'=' '{print $2}' | sort | uniq -c | sort -rn # └ 抽出"code=数字" └────── 取数字 └────── 排序 └ 去重计数 └ 按次数排 # 统计每个接口的平均响应时间(awk 单挑) # log 格式: 接口 耗时ms awk '{count[$1]++; sum[$1] += $2} END {for (k in sum) printf "%s: %.0fms\n", k, sum[k]/count[k]}' access.log
awk '{print $0}' file # $0 是整行 awk '{print $1}' file # $1 是第 1 个字段(不是 $0)
这是 awk 与大多数编程语言(数组从 0 开始)的不同之处。$0 被「整行」占用了,字段从 $1 起。
echo "a b c" | awk '{print $2}' # 输出 b: 连续空格当一个分隔符 echo "a b c" | awk -F' ' '{print $2}' # 输出空: 指定单个空格后, 行为首的两个空格被当作分隔
awk 默认分隔符是「连续的空白字符(空格/制表符)」,这通常正是你想要的。但如果你用 -F' ' 显式指定单个空格,行为会变(它把连续空格当多个分隔符,产生空字段)。通常不需要显式指定 -F' '。
awk 脚本用单引号包裹,但你想在脚本里用 Shell 变量时,单引号会挡住 $ 展开。两种解法:
# 法 1: 用 -v 把 shell 变量传给 awk 变量(推荐) threshold=100 awk -v t="$threshold" '$3 > t {print $1}' file # 法 2: 用双引号包裹脚本(麻烦, 易错, 不推荐) awk "$3 > $threshold {print \$1}" file
for (k in sum) 的输出顺序不确定awk '{sum[$1]++} END {for (k in sum) print k, sum[k]}' file | sort -rn -k2 # └ 要排序就管道给 sort
awk 关联数组的遍历顺序不保证固定(通常是哈希序)。要按某列排序,管道给 sort,而不是指望 awk 给你有序输出。
awk 的心智模型是「记录-字段」+「模式-动作」+「BEGIN/主/END」三层,理解了它,awk 就从玄学变成利器。$1/$2/.../$NF,整行是 $0,分隔符用 -F 指定(默认连续空白)。模式 { 动作 },模式为真才执行动作;模式可省略(对所有行),动作可省略(打印整行)。awk 能「算」的根本。GROUP BY:{sum[$1]+=$2} END{for(k in sum) print k,sum[k]} 是一行分组统计的经典。$1 起($0 是整行)、默认分隔符是连续空白(别显式写 -F' ')、Shell 变量用 -v 传入、数组遍历顺序不确定要靠 sort。下一节我们补齐「切分、排序与去重」三件套(cut/sort/uniq),把 awk 之外的字段处理工具讲完。至此你已经掌握了三巨头中的两个(grep 已学、awk 刚学),第 03 节的 sed(按行改)和 05 节的 cut/sort/uniq(聚合)加进来,你就集齐了完整的「文本处理流水线」工具箱。