跳到主要内容

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','
  • 常用内置变量:
    • 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 动作。第二条命令直接指定 actionprint $0
  • 任务:打印行号 (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 > 75000awk 会自动尝试将 $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_salaryname 赋值。

第四步:总结与后续学习

通过以上实践,你应该掌握了 awk 的以下核心能力:

  • 文本行和字段处理: 读取文件,按分隔符切分字段。
  • 条件逻辑 (Pattern): 根据字段内容、行号、正则表达式等条件筛选数据。
  • 数据操作 (Action): 打印、格式化输出、进行数学运算、使用变量。
  • 流程控制: 利用 BEGINEND 执行初始化和收尾工作。
  • 数据聚合: 使用关联数组进行计数、求和、分组统计等。
  • 状态保持: 在处理不同行之间维护信息(如查找最大/最小值)。

后续学习建议:

  1. 深入实践: 修改上述例子,尝试不同的条件、输出格式、计算逻辑。
  2. 探索更多内置变量和函数:FILENAME, FNR, length(), substr(), split(), tolower(), toupper() 等。
  3. 编写 AWK 脚本: 对于复杂的任务,将 awk 命令写入一个 .awk 文件,使用 awk -f script.awk inputfile 执行。
  4. 学习 AWK 流程控制语句:if-else, while, for (除了 for-in 数组遍历)。
  5. 查阅文档: man awk 是最权威的参考。在线搜索 "awk tutorial", "awk examples", "awk cheat sheet" 等。