4. awk 入门:字段分割、内置变量、基本模式与动作

说真的,在接触 HSM 日志之前,我对 awk 的认知仅限于“一个能把文本切成列的工具”。直到有一次,我需要从几十万行 HSM 审计日志里提取操作类型、用户 ID 和时间戳,还得算出每种操作的出现次数。用 Python 写脚本?太慢了。用 grep 加 cut?碰到字段顺序乱掉的情况就崩了。

后来我花了一个下午老老实实啃 awk,才发现它天生就是干这个活的。嗯,这一章我们就来聊聊 awk 最核心的三个能力:怎么切字段、有哪些内置变量、以及怎么用模式和动作来过滤和变换数据。

4.1 字段分割:awk 的核心本领

awk 默认把一行文本按空格或制表符分割成多个字段。字段编号从 $1 开始,$2 是第二个字段,$0 代表整行。你想想看,这比手写 cut -d' ' 灵活太多了。

举个例子,假设 HSM 日志里有一行:

2025-03-21 14:30:22 HSM_1 key_generate user_admin success

用 awk 打印时间戳和操作类型:

awk '{print $2, $3}' hsm.log

$2 是时间,$3 是 HSM 设备名,$4 是操作。输出结果像这样:

14:30:22 key_generate
个人小习惯:我每次写 awk 字段提取时,会先用 {print NF} 看一眼当前行有几个字段,确认不会把 $5 写成 $6。特别是日志格式突然多了个字段的情况,很容易踩坑。

如果字段之间的分隔符不是空格呢?比如 HSM 的 CSV 格式日志,字段用逗号隔开。这时候就用 -F 指定分隔符:

awk -F',' '{print $1, $3}' hsm.csv

更复杂的情况也用得到——日志里字段之间可能有多个空格,或者制表符和空格混用。awk 默认会把连续的空格/制表符当作一个分隔符处理,这比用 cut -d' ' 舒服多了。我在项目里遇到过日志格式不规范,字段间距不统一,用 cut 解析出来的数据全是错的,换成 awk 就没问题了。

分隔符类型 命令行写法 示例
空白(默认) awk '{print $1}' 连续空格/制表符自动合并
逗号 awk -F',' '{print $2}' CSV 文件专用
冒号 awk -F':' '{print $1}' 类似 /etc/passwd 的格式
正则表达式 awk -F'[,;]' '{print $1}' 逗号或分号都算分隔符

4.2 内置变量:awk 自带的信息牌

awk 预设了一些变量,能直接告诉你当前处理到第几行、总共有多少字段、当前行是什么——不用你自己去计数。我最常用的四个是 NR、NF、FS 和 OFS。

  • NR:当前已读取的记录(行)数。
  • NF:当前行的字段数量。
  • FS:输入字段分隔符,默认是空格/制表符。
  • OFS:输出字段分隔符,默认是空格。

我来给你看个实际场景。统计 HSM 日志中操作失败的次数:

awk '$NF == "fail" {count++} END {print "Fail count:", count}' hsm.log

这里的 $NF 用了嵌套写法——先拿 NF 得到最后一个字段的位置,再通过 $ 引用那个字段。这种用法在处理固定列数但列名不固定时特别有用。

我曾经踩过的坑:在 awk 脚本里给 FS 赋值时,如果写 FS="," 但后面又换了 FS=":",awk 不会自动重新解析已经读过的行。只有新读入的行才会用新的分隔符。所以如果要动态改变分隔符,记得在读取新行之前 next 或者重新打开文件。

输出字段分隔符 OFS 常用在重构输出格式。比如原始日志是逗号分隔,但你想输出成制表符分开的表格:

awk 'BEGIN{FS=","; OFS="\t"} {print $1, $3, $5}' hsm.csv

这样一来,输出的字段之间就用制表符隔开了,复制到 Excel 里直接能识别成列。我在做日志报告时经常这么干,比用手动替换快得多。

4.3 基本模式与动作:让 awk 只处理你关心的行

awk 的基本语法是 模式 { 动作 }。模式控制哪些行被执行动作,动作就是你要对匹配到的行做什么。

没有模式时,所有行都会执行动作,比如前面的 {print $1}。加上模式后,awk 就变成了一个“带条件的过滤器”。

模式有很多种,最常见的两种是:

  • 行范围模式:按行号匹配,如 NR >= 10 && NR <= 20。
  • 正则表达式模式:按内容匹配,如 /key_generate|sign/。

举个例子,只处理第 100 到第 200 行之间的日志,并且只打印包含“fail”的行:

awk 'NR >= 100 && NR <= 200' hsm.log | awk '/fail/ {print $0}'

当然你也可以写在一个 awk 里:

awk 'NR >= 100 && NR <= 200 && /fail/ {print NR, $0}' hsm.log

这样输出的每一行前面都会带上行号,方便你定位到出错的那条日志。我记得有一次系统告警说 HSM 密钥生成失败,我从几万行日志里用这个命令五分钟就定位到了问题行。

再看一个更实用的场景:对特定操作类型统计次数。HSM 日志里有 key_generate、sign、verify 三种操作,我只想统计 sign 和 verify 的出错次数:

awk '/sign|verify/ && /fail/ {count++} END {print "Sign/Verify fails:", count}' hsm.log
核心思路:模式与动作的搭配决定 awk 脚本的结构。把过滤条件放在模式里,把计算或输出放在动作里。这样 awk 就变成了一个“扫描->过滤->处理”的流水线,一步到位。

模式动作还有一个经典用法——BEGIN 和 END 两个特殊模式。BEGIN 在读取任何行之前执行,适合初始化变量、设置表头;END 在所有行处理完成后执行,适合输出汇总结果。前面统计 fail 次数的例子里,END 就派上了用场。

4.4 本章小结

awk 入门其实就三件事:知道怎么切字段($1、$NF、-F),记住内置变量(NR、NF、FS、OFS),学会组合模式与动作。这三样东西加起来,就能应付 HSM 日志解析中绝大部分的数据提取和统计需求了。

你想想看,以前用 grep+cut+wc 组合拳才能做的事,现在一条 awk 命令搞定。而且 awk 脚本可以写在文件里复用,不会因为键盘敲错一个管道就全盘重来。

awk 核心知识结构图 awk 核心知识结构 字段分割 内置变量 模式与动作 $1 $2 ... $NF -F 指定分隔符 FS/OFS 动态控制 NR: 行号 NF: 字段数 FS/OFS: 分隔符控制 BEGIN { } /pattern/ { action } END { } 三者结合:用模式过滤行,用字段分割提取列,用内置变量跟踪状态 一条 awk 命令完成 过滤 → 提取 → 统计 全流程