第4章:文本处理三剑客(中):awk命令基础、字段分割与打印、BEGIN/END模式、内置变量(NR, NF, FS)

awk 这玩意儿,说实话我第一次接触时觉得它挺怪的。明明是个命令,写起来却像在写 C 语言。但用久了你会发现,它简直就是日志分析的瑞士军刀。今天咱们就把它掰开揉碎了讲清楚。

4.1 awk 到底是个啥?

awk 不是缩写,是三个发明人的姓氏首字母:Aho、Weinberger、Kernighan。嗯,就是写《C程序设计语言》的那位 Kernighan。所以 awk 的语法风格跟 C 很像,这就不奇怪了。

它的核心思想很简单:逐行读取文件,按模式匹配,执行动作。说白了就是:你给我一个规则,我帮你从文本里把想要的东西拎出来。

awk 的基本语法:

awk 'pattern { action }' 文件名

pattern 是匹配条件,action 是要执行的操作。如果 pattern 省略,则对所有行执行 action。

我在项目中遇到过最典型的场景:线上排查问题时,几百兆的日志文件,用 grep 只能找到行,但 awk 能直接帮你把关键字段提取出来,还能做统计。这效率差距不是一星半点。

4.2 字段分割与打印

awk 默认按空格或制表符分割字段。每行被分割后,$1 表示第一个字段,$2 表示第二个,以此类推。$0 表示整行。

举个例子,咱们有个 access.log,记录的是:

192.168.1.1 GET /index.html 200 1234
192.168.1.2 POST /login 302 56
192.168.1.3 GET /about.html 200 789

想提取 IP 和状态码?一行搞定:

awk '{print $1, $4}' access.log

输出:

192.168.1.1 200
192.168.1.2 302
192.168.1.3 200

你看,$1 是 IP,$4 是状态码。中间用逗号分隔,awk 会自动帮你加个空格。我个人习惯用逗号分隔字段,这样输出格式更干净。

小技巧:如果字段之间是其他分隔符,比如冒号或逗号,可以用 -F 参数指定。例如:awk -F':' '{print $1}' /etc/passwd

4.3 BEGIN 和 END 模式

这两个模式是 awk 的精髓之一。BEGIN 在处理任何行之前执行,END 在所有行处理完之后执行。说白了就是:开始前做点准备,结束后做点收尾。

我经常用 BEGIN 来打印表头,用 END 来做汇总统计。比如:

awk 'BEGIN {print "IP\t\t状态码\t大小"} 
     {print $1"\t"$4"\t"$5} 
     END {print "--- 报告结束 ---"}' access.log

输出:

IP		状态码	大小
192.168.1.1	200	1234
192.168.1.2	302	56
192.168.1.3	200	789
--- 报告结束 ---

为什么会这样?因为 BEGIN 块在读取文件前就执行了,所以表头在最上面。END 块在所有行处理完后执行,所以收尾信息在最后。

我曾经在排查一个慢接口问题时,用 awk 的 END 模式统计了所有请求的平均响应时间,一下子就定位到了问题接口。嗯,这招很实用。

4.4 内置变量:NR、NF、FS

awk 内置了几个非常常用的变量,掌握了它们,你的 awk 功力能提升一大截。

变量 含义 示例
NR 当前行号(从1开始) NR==1 表示第一行
NF 当前行的字段数 NF 表示最后一个字段
FS 字段分隔符(默认空格) FS=":" 设置冒号为分隔符

NR 的典型用法:跳过表头或只处理特定行。

# 跳过第一行表头,从第二行开始处理
awk 'NR>1 {print $1}' data.txt

# 只处理第10到第20行
awk 'NR>=10 && NR<=20 {print $0}' log.txt

NF 的典型用法:获取最后一个字段,或者判断字段数量。

# 打印每行的最后一个字段
awk '{print $NF}' data.txt

# 只处理字段数大于3的行(过滤掉不完整的行)
awk 'NF>3 {print $0}' messy.log

FS 的典型用法:动态设置分隔符,或者配合 -F 使用。

# 在 BEGIN 中设置分隔符
awk 'BEGIN {FS=":"} {print $1}' /etc/passwd

# 等价于 -F 参数
awk -F':' '{print $1}' /etc/passwd

避坑指南:我曾经在写脚本时,把 NR 和 NF 搞混了。NR 是行号,NF 是字段数。你想想看,如果日志里某行字段数不固定,用 NF 来取最后一个字段就特别方便。但如果你用 NR 去取最后一个字段,那就闹笑话了。

4.5 实战:日志分析三板斧

光说不练假把式。咱们来几个真实场景,看看 awk 怎么用。

场景一:统计每个 IP 的访问次数

awk '{count[$1]++} END {for (ip in count) print ip, count[ip]}' access.log

这里用了一个关联数组 count,以 IP 为键,访问次数为值。END 块里遍历数组输出结果。

场景二:找出状态码不是 200 的请求

awk '$4 != 200 {print $1, $4, $3}' access.log

直接拿 $4 跟 200 比较,不匹配的就打印出来。简单粗暴。

场景三:计算所有请求的总流量

awk '{total+=$5} END {print "总流量:", total, "字节"}' access.log

$5 是请求大小,累加到 total 变量里。END 块输出结果。

核心要点:awk 的数组和循环能力让它不仅能做文本提取,还能做简单的数据统计。很多用 Python 写的小脚本,其实用 awk 一行就能搞定。

4.6 知识体系图

下面这张图帮你理清 awk 的核心逻辑:

awk 核心知识体系 输入文件 / 标准输入 BEGIN 模式(预处理) 主处理循环(逐行读取) 字段分割 → 模式匹配 → 执行动作 内置变量:NR(行号) NF(字段数) FS(分隔符) END 模式(后处理) 输出结果

这张图展示了 awk 的完整执行流程:先执行 BEGIN 做初始化,然后逐行读取文件进行字段分割和模式匹配,最后执行 END 做收尾工作。记住这个流程,awk 就不难理解了。

4.7 总结

awk 的核心就三件事:字段分割、模式匹配、动作执行。BEGIN 和 END 是它的两个特殊模式,NR、NF、FS 是三个最常用的内置变量。

我个人觉得,awk 最大的价值在于:用最少的代码,做最多的事。很多需要写循环、判断的文本处理任务,awk 一行就能搞定。你想想看,排查问题时,时间就是生命,能少敲几个字母都是赚的。

下一章咱们继续聊 sed,另一个文本处理利器。到时候你会发现,awk 和 sed 配合起来,简直无敌。

课后练习:找一个你手头的日志文件,试试用 awk 统计一下每个 IP 的访问次数,或者找出响应时间最长的前10个请求。动手练一练,比看十遍都管用。

可可精品资料库