1. awk 是什么
awk 不是某一个功能的缩写--它是三位作者姓氏的首字母:A ho,W einberger 和 K ernighan.它是一个专为文本处理设计的小型编程语言 ,内置于 Unix 系统中.
如果说 grep 是"找东西的手电筒",sed 是"批量修改的流水线",那么 awk 就是"带计算器,记事本和统计表格的办公桌"--它不只是找和改,还能按列切分,做算术,统计聚合,格式化输出 .
1.1 grep · sed · awk 定位图
grep
sed
awk
核心功能
按行搜索
按行修改
按列处理 + 计算
列感知
无
无(需靠正则模拟)
原生支持
算术能力
无
无
完善
变量与流程控制
无
极有限(保持空间)
完整编程语言
典型一行命令
grep ERROR log
sed 's/a/b/g' file
awk '{print $1}' file
一句话定位 :当处理的文本有"列"的概念(日志,CSV,ps 输出,任何按分隔符组织的文本),并且需要按列筛选,按列计算,按列统计时--awk 是最正解.
2. 执行模型:逐行 × 逐列
awk 和 sed 一样是逐行处理,但它多了一步自动拆列 --每读入一行,就按分隔符切成若干字段(fields),可以直接用 $1,$2... 引用它们.
┌──────────┐ │ 行进入 │ └──────────┘ │ ▼ ┌───────────────────────┐ │ 拆分成字段 (按 FS 分隔) │ └───────────────────────┘ │ ▼ ┌───────────────────────┐ │ 模式匹配? (Pattern) │ └───────────────────────┘ │ "不匹配" ▼ ┌──────────┐ │ 跳过 │ └──────────┘ │ ▼ ┌─────────────────────┐ │ 执行动作 (Action) │ └─────────────────────┘ │ ▼ ┌──────────┐ │ 读下一行 │ └──────────┘ │ ▼ ┌──────────┐ │ 还有下一行? │ └──────────┘ │ "否" ▼ ┌───────────────┐ │ 执行 END 块 → 结束 │ └───────────────┘
2.1 一行数据走完 awk 的全过程
假设文件有三行,执行 awk '{print $2}' file:
第 1 行 "Alice 95 A" 进入 → 拆字段:$1="Alice", $2="95", $3="A" → 模式为空(所有行都匹配) → 执行动作:print $2 → 输出 "95" 第 2 行 "Bob 87 B" 进入 → 拆字段:$1="Bob", $2="87", $3="B" → 执行动作:print $2 → 输出 "87" ...依此类推
2.2 Pattern { Action } -- awk 的双引擎
awk 脚本的核心结构是:
模式 { 动作 } # ↑ ↑ # 筛选 执行 # 哪行 做啥
两个部分都是可选的 :
写法
含义
awk '/ERROR/ {print $1}'
有模式有动作:匹配 ERROR 的行,打印第 1 列
awk '{print $1}'
无模式:所有行 都执行 print $1
awk '/ERROR/'
无动作:匹配 ERROR 的行默认执行 print $0 (打印整行)
awk 的默认动作 = print $0
当只写模式不写动作时,awk 等价于行级别的 grep:awk '/ERROR/' log 和 grep ERROR log 结果一样.但 awk 能写更丰富的模式--比如 awk '$3 > 500'(第 3 列大于 500 的行),这是 grep 做不到的.
3. 字段:按列处理的核心
3.1 $1 $2 ... $NF $0
符号
含义
$0
整行原文
$1, $2, $3, ...
第 1 列,第 2 列,第 3 列...
$NF
最后一列 (NF = Number of Fields,字段总数)
$(NF-1)
倒数第二列
$ cat students.txt Alice 95 A Bob 87 B Carol 92 A Dave 78 C $ awk '{print $1}' students.txt Alice Bob Carol Dave $ awk '{print $1, $3}' students.txt Alice A Bob B Carol A Dave C $ awk '{print $NF}' students.txt A B A C $ awk '{print $(NF-1)}' students.txt 95 87 92 78 $ awk '{print $0}' students.txt
$1 不是 Shell 的 $1(脚本参数),不是 $PATH 里的变量引用--它是 awk 内部用于取字段的语法.Shell 变量在 awk 中需要用 -v 传进去:awk -v name="$USER" '{print name}'.
3.2 FS:自定义分隔符
awk 默认按任意空白字符 (空格,制表符)分隔.处理 CSV,/etc/passwd,ps 输出等不同格式时,通过 -F 指定分隔符:
$ awk -F: '{print $1}' /etc/passwd $ awk -F, '{print $1, $3}' data.csv $ awk 'BEGIN {FS=":"} {print $1}' /etc/passwd $ echo "a|||b|||c" | awk -F'[|]{3}' '{print $2}' b
3.3 默认分隔符的神奇之处
awk 默认按空白分隔有一个特别的处理:连续多个空白会被视为一个分隔符 ,行首行尾空白也被忽略.这和 cut 的行为完全不同:
$ echo " alice 30 engineer " | awk '{print $1, $2, $3}' alice 30 engineer $ echo " alice 30 engineer " | cut -d' ' -f1 $ echo " alice 30 engineer " | tr -s ' ' | cut -d' ' -f2 alice
OFS (Output Field Separator,输出字段分隔符)控制在 print $1, $2 中逗号用什么连接:
$ awk '{print $1, $2}' students.txt Alice 95 $ awk -v OFS=, '{print $1, $2}' students.txt Alice,95
4. 记录与行号:NR · FNR · RS
变量
全称
含义
NR
Number of Records
已处理的总行数(跨文件累加)
FNR
File Number of Records
当前文件 内的行号(每个文件重置为 1)
RS
Record Separator
记录分隔符(默认 \n,即一行 = 一条记录)
$ awk '{print NR, $0}' students.txt 1 Alice 95 A 2 Bob 87 B 3 Carol 92 A 4 Dave 78 C $ awk '{print "总行号:", NR, "文件内行号:", FNR, "内容:", $0}' file1.txt file2.txt $ awk 'NR>=3 && NR<=5 {print $0}' file.txt $ awk 'NR>2 {print}' data.csv
5. 模式:筛选哪些行
5.1 BEGIN 与 END -- awk 的特殊"锚点"
BEGIN 在读任何行之前 执行一次,END 在所有行处理完后 执行一次.它们不参与逐行循环:
文件没开始读呢 → BEGIN { 初始化变量,打印表头 } 文件逐行读入 → { 每行处理 } → /pattern/ { 匹配的行 } 文件读完了 → END { 打印汇总结果 } → awk 退出
$ awk ' BEGIN { print "--- 学生成绩 ---" } { sum += $2; count++ } # 每行累加第 2 列 END { print "----------------" print "总分:", sum print "人数:", count print "平均:", sum/count } ' students.txt --- 学生成绩 --- ---------------- 总分: 352 人数: 4 平均: 88
5.2 正则模式
$ awk '/ERROR/' app.log $ awk '$3 ~ /ERROR/' app.log $ awk '$3 !~ /DEBUG/' app.log $ awk '$1 ~ /^[AB]/' students.txt Alice 95 A Bob 87 B
~ 表示"匹配正则",!~ 表示"不匹配正则".这是 awk 独有的字段级正则能力--grep 只能匹配整行.
5.3 比较模式
awk 支持完整的比较运算符,可以直接用数字或字符串比较作为模式:
$ cat employees.txt Alice 30 85000 Bob 25 62000 Carol 35 95000 Dave 28 72000 $ awk '$3 > 80000' employees.txt Alice 30 85000 Carol 35 95000 $ awk '$2 <= 28 && $3 >= 70000' employees.txt Dave 28 72000 $ awk '$1 == "Alice"' employees.txt Alice 30 85000 $ awk '$1 != "Bob"' employees.txt
5.4 空模式与无条件动作
不写模式 = 每一行都执行:
$ awk '{print NR, $1}' students.txt 1 Alice 2 Bob 3 Carol 4 Dave
6. 动作:做什么
6.1 print 与 printf
print 简单直接,自动加换行.逗号分隔的多个参数之间自动插入 OFS:
$ awk '{print $1, $2, $3}' students.txt $ awk '{print $1 $2 $3}' students.txt Alice95A
printf 提供精确的格式化控制,语法和 C 语言一致:
$ awk '{printf "%-10s %5d\n", $1, $2}' students.txt Alice 95 Bob 87 Carol 92 Dave 78 $ awk '{printf "%.2f\n", $2}' students.txt 95.00 87.00 92.00 78.00
格式符
含义
%s
字符串
%d
整数
%f
浮点数
%.2f
浮点数,保留 2 位小数
%-20s
左对齐,占 20 字符宽度
%10d
右对齐,占 10 字符宽度
\n
换行(printf 不会自动加)
6.2 变量与运算
awk 的变量无需声明 ,首次使用时自动初始化为 0 或空字符串:
$ awk '{sum += $2} END {print sum}' students.txt 352 $ awk '{s = s $1 " "} END {print s}' students.txt Alice Bob Carol Dave $ awk '{print $1, $2 * 2}' students.txt Alice 190 Bob 174 Carol 184 Dave 156 $ awk '{print NR, $1; count++} END {print "Total:", count}' students.txt $ awk '{full = $1 " (" $3 ")"; print full}' students.txt Alice (A) Bob (B) Carol (A) Dave (C)
6.3 条件与循环
$ awk '{ if ($2 >= 90) print $1, "优秀" else if ($2 >= 80) print $1, "良好" else print $1, "加油" }' students.txt Alice 优秀 Bob 良好 Carol 优秀 Dave 加油 $ awk '{ for (i=1; i<=NF; i++) printf "字段%d: %s ", i, $i print "" # 换行 }' students.txt $ awk '{i=1; while (i<=NF) {print NR":"i,$i; i++}}' students.txt
7. 内建变量速查表
变量
含义
默认值
NR
已处理的记录总数(跨文件累加)
-
FNR
当前文件内的记录号(每换文件重置)
-
NF
当前记录的字段数
-
FS
输入字段分隔符(Field Separator)
任意空白
OFS
输出字段分隔符
空格
RS
输入记录分隔符(Record Separator)
\n
ORS
输出记录分隔符
\n
FILENAME
当前正在处理的文件名(只读)
-
ARGC
命令行参数数量
-
ARGV
命令行参数数组
-
8. 常用函数
8.1 字符串函数
函数
含义
示例
length(s)
字符串长度
{print length($1)}
substr(s, i, n)
子串:从位置 i 取 n 个字符
{print substr($0, 1, 10)}
index(s, t)
t 在 s 中首次出现的位置
index($0, "ERROR")
split(s, arr, sep)
按 sep 拆分 s 到数组 arr
split($0, a, ":")
gsub(r, t, s)
全局替换
{gsub(/old/, "new")}
sub(r, t, s)
替换(仅第一个匹配)
{sub(/^ +/, "")}
tolower(s)
转小写
{print tolower($1)}
toupper(s)
转大写
{print toupper($1)}
sprintf(fmt, ...)
格式化到字符串(不打印)
sprintf("%.2f", $2)
$ awk 'length($1) > 4' students.txt Alice 95 A Carol 92 A $ echo "2024-06-16" | awk '{print substr($0, 1, 4)}' 2024 $ awk -F: '{split($5, gecos, ","); print $1, gecos[1]}' /etc/passwd $ echo "Hello World" | awk '{gsub(/[aeiouAEIOU]/, "*"); print}' H*ll* W*rld $ awk '{print toupper($1)}' students.txt ALICE BOB CAROL DAVE
8.2 数值函数
函数
含义
int(x)
截断取整(向零舍入)
sqrt(x)
平方根
rand()
随机数 [0, 1)
srand(x)
设置随机种子
sin(x) / cos(x)
三角函数(弧度)
log(x) / exp(x)
自然对数 / e 的幂
9. 关联数组:统计与去重
awk 的数组和常规编程语言不同--它用任意字符串作键 (不是从 0 开始的数字索引),这天然适合做统计和去重.
9.1 数组基础
$ awk '{ count[$1]++ } END { print count["Alice"] }' students.txt 1
9.2 词频统计--awk 的招牌操作
$ awk '{count[$1]++} END {for (ip in count) print ip, count[ip]}' access.log 10.0.1.5 423 10.0.2.8 156 192.168.1.100 89 $ awk '{status[$9]++} END {for (s in status) print s, status[s]}' access.log 200 15234 404 423 500 12
count[$1]++ 就像是给每一行打了一个即时标签贴纸,然后把相同标签的贴纸堆在一起.
第一行 "Alice" → 打开一个叫 "Alice" 的抽屉,放进去 +1;第二行又是 "Alice" → 打开同一个抽屉,再 +1.
最终,抽屉标签(键)就是所有独一无二的值,抽屉里的内容(值)就是每个值出现的次数.
9.3 遍历数组
$ awk '{scores[$1]=$2} END { for (name in scores) printf "%-10s %s\n", name, scores[name] }' students.txt Alice 95 Bob 87 Carol 92 Dave 78 $ awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head
awk 的数组不支持直接排序
for (key in array) 的遍历顺序是未定义的.需要排序就把输出管道给 sort,或者自己维护一个索引数组用 asort / asorti(GNU awk 扩展,macOS 不支持).这个局限恰好体现了 Unix 哲学--每个工具做好一件事,awk 负责统计,sort 负责排序.
10. 常用配方(Recipes)
awk '{print $2, $5}' data.txt awk '{print $2, $1}' data.txt awk '{print $NF}' data.txt awk '$3 > 100' data.txt awk '$1 ~ /^[a-zA-Z]/' data.txt awk 'NF == 5' data.csv awk '{sum += $3} END {print sum}' data.txt awk '{sum += $3; n++} END {print sum/n}' data.txt awk 'NR==1 {max=$3; min=$3} $3>max {max=$3} $3<min {min=$3} END {print "max:", max, "min:", min}' data.txt
快速回顾
awk = 逐行处理 + 自动拆列 :每一行按 FS 分隔符切成 $1 $2 ... $NF,$0 是整行
核心结构 :Pattern { Action }.模式筛选哪些行,动作决定做什么--两部分都可省略
BEGIN/END :BEGIN 在读任何行前执行(初始化,打印表头),END 在所有行后执行(打印汇总)
变量无需声明 :首次使用自动初始化为 0 或空串.支持 + - * / % ^ 算术运算和直接字符串拼接
内建变量 :NR(总行号),NF(当前行字段数),FS(输入分隔符),OFS(输出分隔符)
字段级正则 :$1 ~ /pattern/ 匹配指定列--grep 做不到的 awk 做到了
关联数组 :以字符串为键,天然适合词频统计和去重--count[$1]++ 是最经典的 awk 惯用语
排序交给 sort :for-in 顺序不定,把结果管道给 sort,Unix 哲学各司其职
三者互补 :grep = 行搜索,sed = 行修改,awk = 列处理 + 计算 + 统计--管道串联威力最大
动手练习
列提取 :用 ps aux | awk '{print $1, $3}' 列出所有进程的用户名和 CPU 占用率,按 CPU 降序排列
分隔符 :用 awk -F: '{print $1, $7}' /etc/passwd 提取所有用户名及其登录 Shell,然后用 grep 找出用 bash 的用户
CSV 处理 :创建一份 CSV 数据(逗号分隔),练习 awk -F, '{print $2}' 和自定义 OFS
BEGIN/END 统计 :用 awk 计算下载文件夹的总大小:ls -l ~/Downloads | awk '{sum += $5} END {print sum/1024/1024 " MB"}'
去重 :用 awk '!seen[$0]++' 对一个有重复行的文件去重,理解这个神奇的一行命令
词频统计 :用 awk '{count[$1]++} END {for (k in count) print count[k], k}' 统计某个日志文件中各 IP 的访问次数,用 sort -rn | head 取 Top 10
格式化输出 :用 awk '{printf "%-15s %5d\n", $1, $2}' 格式化输出一些数据,尝试不同的宽度和对齐方式