awk 核心知识点
本指南旨在帮助你通过实践学习 awk
的主要核心功能。
第一步:准备示例文本文件
首先,在你的电脑上创建一个名为 data.txt
的文本文件,内容如下:
ID,Name,Department,Salary,JoinDate
101,Alice,IT,80000,2022-03-15
102,Bob,Sales,65000,2021-11-01
103,Charlie,IT,82000,2023-01-20
104,David,HR,55000,2022-08-10
105,Eve,Sales,70000,2021-12-05
106,Frank,IT,95000,2020-05-30
107,Grace,HR,60000,2023-05-01
108,Heidi,Sales,68000,2022-07-18
文件说明:
- 这是一个简单的 CSV (逗号分隔值) 格式的员工信息表。
- 第一行是表头。
- 字段包括:ID, 姓名, 部门, 薪水, 入职日期。
第二步:AWK 核心概念回顾
- 基本工作模式:
awk 'pattern { action }' filename
pattern
: 匹配条件,决定是否对当前行执行action
。省略则对所有行执行action
。action
: 一系列操作,通常是处理和打印数据。省略则默认执行print $0
(打印整行)。
- 核心概念:
- 记录 (Record):
awk
处理的基本单位,默认是文件的每一行。用$0
表示整个记录。 - 字段 (Field): 记录中被字段分隔符分开的部分。用
$1
,$2
,$3
, ... 表示。 - 字段分隔符 (FS): 用于分割字段的字符。默认为空格或制表符。可以通过
-F
命令行选项或在BEGIN
块中设置FS
变量来更改。对于data.txt
,我们需要指定-F','
。
- 记录 (Record):
- 常用内置变量:
NR
: Number of Record,当前处理的记录(行)的编号,从 1 开始。NF
: Number of Fields,当前记录包含的字段数量。FS
: Field Separator,输入字段分隔符。OFS
: Output Field Separator,输出字段分隔符 (影响print
语句中逗号分隔的输出)。
第三步:AWK 实践案例 (整合与进阶)
请在你的终端(命令行)中,确保 data.txt
在当前目录,然后逐一执行以下命令并观察结果。
场景 1:基础打印与字段访问
-
任务:打印所有内容
- 命令 1 (利用默认 action):
awk '1' data.txt
- 命令 2 (显式打印整行):
awk '{print $0}' data.txt
- 解释:
1
是一个永远为真的pattern
,触发默认的print $0
动作。第二条命令直接指定action
为print $0
。
- 命令 1 (利用默认 action):
-
任务:打印行号 (NR) 和所有内容 ($0)
- 命令:
awk -F',' '{print NR, $0}' data.txt
- 解释:
NR
是内置变量,代表当前行号。-F','
设置逗号为字段分隔符,虽然这里print $0
不直接受影响,但好习惯是处理 CSV 时加上。
- 命令:
-
任务:打印第 2 列 (姓名) 和第 4 列 (薪水)
- 命令:
awk -F',' '{print $2, $4}' data.txt
- 解释:
-F','
告诉awk
使用逗号分割字段。$2
和$4
分别引用第二个和第四个字段。print
中的逗号默认输出一个空格 (OFS
的默认值)。
- 命令:
场景 2:根据条件过滤行 (Pattern 的使用)
-
任务:打印 IT 部门 (第 3 列是 "IT") 的员工姓名和部门
- 命令:
awk -F',' '$3 == "IT" {print $2, $3}' data.txt
- 解释:
pattern
是$3 == "IT"
。只有当第三个字段精确匹配 "IT" 时,才执行花括号中的action
。
- 命令:
-
任务:打印工资 (第 4 列) 高于 75000 的员工姓名和薪水
- 命令:
awk -F',' '$4 > 75000 {print $2, $4}' data.txt
- 解释:
pattern
是$4 > 75000
。awk
会自动尝试将$4
的值作为数字进行比较。
- 命令:
-
任务:跳过表头 (行号 > 1),打印工资高于 75000 的员工姓名和薪水
- 命令:
awk -F',' 'NR > 1 && $4 > 75000 {print $2, $4}' data.txt
- 解释:
NR > 1
是一个额外的条件,确保只处理数据行。&&
是逻辑与操作符,两个条件都为真才执行action
。
- 命令:
-
任务:打印 2022 年 (第 5 列包含 "2022") 加入的员工姓名和入职日期
- 命令:
awk -F',' '$5 ~ /2022/ {print $2, $5}' data.txt
- 解释:
pattern
是$5 ~ /2022/
。~
是正则表达式匹配操作符。/2022/
是一个正则表达式,匹配任何包含 "2022" 的字符串。 - 思考: 如果只想匹配以
2022-
开头的日期,应使用$5 ~ /^2022-/
。
- 命令:
-
任务:打印 IT 部门 (第 3 列) 且工资大于 80000 (第 4 列) 的员工姓名、部门和薪水
- 命令:
awk -F',' 'NR > 1 && $3 == "IT" && $4 > 80000 {print $2, $3, $4}' data.txt
- 解释: 使用
&&
连接多个条件,形成更复杂的pattern
。
- 命令:
场景 3:使用 BEGIN 和 END 块
- 任务:在处理文件前打印标题,处理完所有行后打印结束语
- 命令:
awk -F',' 'BEGIN { print "--- Employee Report Start ---" } { print NR, $0 } END { print "--- Employee Report End ---" }' data.txt
- 解释:
BEGIN
块中的代码在读取第一行之前执行一次。END
块中的代码在处理完最后一行之后执行一次。中间的{ print NR, $0 }
对(满足pattern
的)每一行执行。
- 命令:
场景 4:计算与统计 (结合 BEGIN/END)
-
任务:计算所有员工 (不含表头) 的工资总和
- 命令:
awk -F',' 'NR > 1 { sum += $4 } END { print "Total Salary:", sum }' data.txt
- 解释:
NR > 1
跳过表头。sum += $4
对每个员工的薪水进行累加(sum
变量会自动初始化为 0)。END
块输出最终的总和。
- 命令:
-
任务:计算所有员工 (不含表头) 的平均工资
- 命令:
awk -F',' 'NR > 1 { sum += $4; count++ } END { if (count > 0) printf "Average Salary: %.2f\n", sum / count; else print "No employee data found."; }' data.txt
- 解释: 在处理每行时,同时累加总工资 (
sum
) 和员工数量 (count
)。END
块计算平均值,并使用printf
格式化输出(保留两位小数%.2f
),同时检查count
以避免除零错误。
- 命令:
场景 5:格式化输出 (printf 和 OFS)
-
任务:使用
printf
格式化打印员工姓名、部门和薪水 (指定对齐和宽度)- 命令:
awk -F',' 'NR > 1 { printf "%-10s %-8s $%d\n", $2, $3, $4 }' data.txt
- 解释:
printf
提供更精细的输出控制。%-10s
: 输出字符串 (s
),左对齐 (-
),宽度为 10。%-8s
: 输出字符串,左对齐,宽度为 8。$%d
: 先输出字面量$
,然后输出整数 (d
)。\n
: 输出换行符。
- 命令:
-
任务:添加格式化的表头后打印
- 命令:
awk -F',' 'BEGIN { printf "%-10s %-8s %s\n", "Name", "Dept", "Salary"; printf "--------------------------\n" } NR > 1 { printf "%-10s %-8s $%d\n", $2, $3, $4 }' data.txt
- 解释: 在
BEGIN
块中使用printf
打印与数据行格式一致的表头和分隔线。
- 命令:
-
任务:打印姓名、部门和薪水,使用制表符 (
\t
) 分隔输出 (使用 OFS)- 命令:
awk -F',' 'BEGIN { OFS="\t" } NR > 1 { print $2, $3, $4 }' data.txt
- 解释: 在
BEGIN
块中设置OFS
(Output Field Separator) 为制表符\t
。当print
语句中使用逗号分隔多个参数时,awk
会在它们之间插入OFS
的值。
- 命令:
场景 6:使用关联数组进行分组统计
-
任务:统计各部门的员工人数
- 命令:
awk -F',' 'NR > 1 { dept_count[$3]++ } END { print "--- Department Counts ---"; for (dept_name in dept_count) print dept_name ":", dept_count[dept_name] }' data.txt
- 解释:
dept_count[$3]++
: 使用部门名称 ($3
) 作为关联数组dept_count
的索引(键)。每次遇到一个部门的员工,对应键的值就加 1。数组和键会自动创建。END { for (...)... }
: 在处理完所有行后,使用for (key in array)
循环遍历关联数组dept_count
的所有键 (dept_name
),并打印每个部门的名称及其统计的人数。
- 命令:
-
任务:计算各部门的平均工资
- 命令:
awk -F',' 'NR > 1 { dept_sum[$3] += $4; dept_count[$3]++ } END { print "--- Department Average Salary ---"; for (d in dept_sum) printf "%-8s Avg Salary: %.2f\n", d, dept_sum[d] / dept_count[d] }' data.txt
- 解释: 同时使用两个关联数组:
dept_sum
按部门累加薪水,dept_count
按部门统计人数。END
块中遍历其中一个数组(它们的键应该是一样的),计算并格式化输出每个部门的平均工资。
- 命令:
场景 7:更复杂的逻辑 - 查找极值
- 任务:找出工资最高的员工及其工资
- 命令:
awk -F',' 'NR == 2 { max_salary = $4; name = $2 } NR > 2 { if ($4 > max_salary) { max_salary = $4; name = $2 } } END { print "--- Highest Salary ---"; print "Name:", name; print "Salary:", max_salary }' data.txt
- 解释:
NR == 2 { max_salary = $4; name = $2 }
: 处理第一个数据行(第二行)时,将其薪水和姓名初始化为当前最高值。NR > 2 { if ($4 > max_salary) ... }
: 对于后续的数据行,如果当前行薪水 ($4
) 大于已记录的最高薪水 (max_salary
),则更新max_salary
和对应的姓名name
。END { ... }
: 输出最终找到的最高工资员工信息。
- 另一种更简洁的初始化方式(推荐):
这种方式利用了
awk -F',' 'NR > 1 { if ($4 > max_salary || NR == 2) { max_salary = $4; name = $2 } } END { print "--- Highest Salary ---"; print "Name:", name; print "Salary:", max_salary }' data.txt
NR == 2
这个条件,确保第一次比较时(或者说,在处理第一条数据时)一定会将max_salary
和name
赋值。
- 命令:
第四步:总结与后续学习
通过以上实践,你应该掌握了 awk
的以下核心能力:
- 文本行和字段处理: 读取文件,按分隔符切分字段。
- 条件逻辑 (Pattern): 根据字段内容、行号、正则表达式等条件筛选数据。
- 数据操作 (Action): 打印、格式化输出、进行数学运算、使用变量。
- 流程控制: 利用
BEGIN
和END
执行初始化和收尾工作。 - 数据聚合: 使用关联数组进行计数、求和、分组统计等。
- 状态保持: 在处理不同行之间维护信息(如查找最大/最小值)。
后续学习建议:
- 深入实践: 修改上述例子,尝试不同的条件、输出格式、计算逻辑。
- 探索更多内置变量和函数: 如
FILENAME
,FNR
,length()
,substr()
,split()
,tolower()
,toupper()
等。 - 编写 AWK 脚本: 对于复杂的任务,将
awk
命令写入一个.awk
文件,使用awk -f script.awk inputfile
执行。 - 学习 AWK 流程控制语句: 如
if-else
,while
,for
(除了for-in
数组遍历)。 - 查阅文档:
man awk
是最权威的参考。在线搜索 "awk tutorial", "awk examples", "awk cheat sheet" 等。