Linux 文本处理三剑客之 awk:取列、取行、过滤与统计计算

文章目录
- [一、认识 awk](#一、认识 awk)
- [1.1 awk 是什么](#1.1 awk 是什么)
- [1.2 awk 和 grep、sed 的区别](#1.2 awk 和 grep、sed 的区别)
- [1.3 awk 的基本语法](#1.3 awk 的基本语法)
- [1.4 BEGIN、主体和 END 的执行顺序](#1.4 BEGIN、主体和 END 的执行顺序)
- 二、准备测试文件
- [2.1 空格分隔版本](#2.1 空格分隔版本)
- [2.2 逗号分隔版本](#2.2 逗号分隔版本)
- [三、awk 语法测试](#三、awk 语法测试)
- [3.1 打印整行内容](#3.1 打印整行内容)
- [3.2 使用 BEGIN 和 END](#3.2 使用 BEGIN 和 END)
- [3.3 print 输出字符串的注意事项](#3.3 print 输出字符串的注意事项)
- [四、awk 获取列](#四、awk 获取列)
- [4.1 字段变量的含义](#4.1 字段变量的含义)
- [4.2 打印指定列](#4.2 打印指定列)
- [4.3 拼接固定格式输出](#4.3 拼接固定格式输出)
- [4.4 使用 NF 获取最后一列](#4.4 使用 NF 获取最后一列)
- [4.5 使用 -F 指定分隔符](#4.5 使用 -F 指定分隔符)
- [五、awk 获取行](#五、awk 获取行)
- [5.1 使用 NR 按行号过滤](#5.1 使用 NR 按行号过滤)
- [5.2 使用比较运算符](#5.2 使用比较运算符)
- [5.3 使用逻辑运算符](#5.3 使用逻辑运算符)
- [5.4 使用正则匹配行](#5.4 使用正则匹配行)
- [5.5 使用字段条件过滤行](#5.5 使用字段条件过滤行)
- [5.6 使用范围条件取连续内容](#5.6 使用范围条件取连续内容)
- [六、awk 取行取列实操](#六、awk 取行取列实操)
- [6.1 提取网卡 IP 地址](#6.1 提取网卡 IP 地址)
- [6.2 提取内存信息](#6.2 提取内存信息)
- [6.3 过滤 /etc/passwd 指定字段](#6.3 过滤 /etc/passwd 指定字段)
- [七、awk 统计计算](#七、awk 统计计算)
- [7.1 统计行数](#7.1 统计行数)
- [7.2 统计空行数量](#7.2 统计空行数量)
- [7.3 对字段求和](#7.3 对字段求和)
- [7.4 分组计数与分组求和](#7.4 分组计数与分组求和)
- [7.5 计算平均值](#7.5 计算平均值)
- [7.6 计算内存可用率](#7.6 计算内存可用率)
- [7.7 查找最大值和最小值](#7.7 查找最大值和最小值)
- 八、常见运维场景
- 九、复习检查点
- 总结
- 十、注意事项
一、认识 awk
1.1 awk 是什么
awk 是一种文本处理工具,也可以理解成一门小型文本处理语言。它会一行一行读取输入内容,然后根据条件执行动作。
和普通命令相比,awk 的能力更像"边读文本边写一点逻辑":
- 可以按列取值;
- 可以按行号过滤;
- 可以用正则筛选;
- 可以做变量赋值;
- 可以做数学运算;
- 可以计数、求和、求平均值;
- 可以在读取文件前后分别执行不同动作。
awk 这个名字来自三位作者的姓氏首字母:Alfred Aho、Peter Weinberger、Brian Kernighan。
日常运维里不用把 awk 当成完整编程语言去背,先抓住一句话就够了:
awk最常用的能力,就是把一行文本切成多列,然后根据行和列做过滤、输出、统计。
1.2 awk 和 grep、sed 的区别
Linux 文本处理三剑客各有侧重点:
| 工具 | 更擅长的事情 | 常见理解 |
|---|---|---|
grep |
按关键字或正则找行 | 找到符合条件的行 |
sed |
按行编辑文本 | 替换、删除、插入、打印 |
awk |
按字段分析文本 | 取列、过滤、计算、格式化输出 |
举个简单例子,如果有一份人员信息:
bash
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
需求不同,适合的工具也不同:
| 需求 | 推荐工具 | 示例 |
|---|---|---|
| 找出包含"男"的行 | grep |
grep '男' test.txt |
| 把"男"替换为"M" | sed |
sed 's/男/M/g' test.txt |
| 只输出姓名和年龄 | awk |
awk '{print $1,$2}' test.txt |
| 统计年龄总和 | awk |
awk '{sum+=$2} END{print sum}' test.txt |
所以学习 awk 时,不要只把它当成"另一个 grep"。它真正强的地方是字段处理和统计计算。
1.3 awk 的基本语法
awk 的常用格式如下:
bash
awk [选项] 'BEGIN{...} 模式{动作} END{...}' 文件
也可以简化成:
bash
awk '条件{动作}' 文件
几个关键部分的作用如下:
| 部分 | 作用 |
|---|---|
BEGIN{...} |
读取文件前执行一次,常用于初始化变量、打印表头 |
模式 |
行过滤条件,可以是行号、正则、字段比较等 |
{动作} |
对满足条件的行执行操作,比如 print、累加、赋值 |
END{...} |
文件读取完成后执行一次,常用于输出统计结果 |
文件 |
要处理的文本文件,也可以来自管道输入 |
最简单的写法是:
bash
awk '{print}' test.txt
这条命令没有写条件,表示每一行都执行 {print}。print 没有指定字段时,默认打印整行。
1.4 BEGIN、主体和 END 的执行顺序

awk 的执行顺序很固定:
- 先执行
BEGIN{},只执行一次; - 开始读取文件,每次读取一行;
- 对当前行判断是否满足模式条件;
- 满足条件就执行
{动作}; - 文件全部读取完成后,执行
END{},只执行一次。
例如:
bash
awk 'BEGIN{print "start"} {print} END{print "end"}' test.txt
可以理解成:
- 还没读文件,先输出
start; - 读取每一行时,把当前行打印出来;
- 所有行读完后,输出
end。
这个执行顺序非常重要。后面做统计时,变量通常在主体部分累加,最后在 END 中统一输出结果。
二、准备测试文件
2.1 空格分隔版本
后面的基础示例先使用一个空格分隔的 test.txt:
bash
cat > test.txt <<'EOF'
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
EOF
查看文件:
bash
[root@atguigu ~]# cat test.txt
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
这份文件一共有 4 列:
| 列号 | 含义 |
|---|---|
| 第 1 列 | 姓名 |
| 第 2 列 | 年龄 |
| 第 3 列 | 性别 |
| 第 4 列 | 手机号码 |
awk 默认把连续空格或制表符当成字段分隔符,所以这种文件可以直接使用 $1、$2、$3、$4 取列。
2.2 逗号分隔版本
实际工作中,很多文件不是空格分隔,而是逗号、冒号、竖线等字符分隔。为了后面演示 -F,可以准备一个逗号分隔版本:
bash
cat > test.csv <<'EOF'
张伟,28,男,13912345678
李芳,34,女,13887654321
王强,42,男,13712345678
赵敏,29,女,13687654321
刘洋,36,男,13512345678
陈静,31,女,13487654321
李明,25,男,13312345678
吴丽,27,女,13287654321
钱伟,39,男,13112345678
孙芳,33,女,13087654321
EOF
对于这种文件,必须告诉 awk 分隔符是逗号:
bash
awk -F',' '{print $1,$2}' test.csv
否则 awk 会按照默认空格分列,结果就不是想要的姓名和年龄。
三、awk 语法测试
3.1 打印整行内容
打印文件全部内容:
bash
[root@atguigu ~]# awk '{print}' test.txt
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
这条命令等价于:
bash
awk '{print $0}' test.txt
$0 表示当前整行内容。没有写字段时,print 默认打印当前整行。
3.2 使用 BEGIN 和 END
在读取文件前输出 123,读取文件后输出 456:
bash
[root@atguigu ~]# awk 'BEGIN{print 123} {print} END{print 456}' test.txt
123
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
456
如果要输出表头,BEGIN 很适合:
bash
[root@atguigu ~]# awk 'BEGIN{print "姓名 年龄 性别 手机号码"} {print}' test.txt
姓名 年龄 性别 手机号码
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
3.3 print 输出字符串的注意事项
print 输出数字时可以不加引号:
bash
awk 'BEGIN{print 123}'
输出普通字符串、中文、英文时,需要加引号:
bash
awk 'BEGIN{print "姓名 年龄 性别 手机号码"}'
错误写法:
bash
awk 'BEGIN{print 姓名 年龄 性别 手机号码} {print}' test.txt
这类写法容易报错,因为 awk 会把没有引号的内容当成变量或表达式来解析。
也可以使用逗号分隔多个输出项:
bash
[root@atguigu ~]# awk 'BEGIN{print "姓名","年龄","性别","手机号码"} {print}' test.txt
姓名 年龄 性别 手机号码
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
这里的逗号不是原样输出逗号,而是告诉 awk:输出多个字段,中间用输出分隔符隔开。默认输出分隔符是空格。
四、awk 获取列

4.1 字段变量的含义
awk 处理每一行时,会自动把这一行切成多个字段:
| 变量 | 含义 |
|---|---|
$0 |
当前整行 |
$1 |
第 1 列 |
$2 |
第 2 列 |
$3 |
第 3 列 |
$NF |
最后一列 |
$(NF-1) |
倒数第 2 列 |
NF |
当前行字段数量 |
NR |
当前处理到第几行 |
这里容易混的是 $NF 和 NF:
NF是字段数量;$NF是最后一列的值。
例如一行内容是:
bash
张伟 28 男 13912345678
那么:
| 表达式 | 结果 |
|---|---|
$1 |
张伟 |
$2 |
28 |
$3 |
男 |
$4 |
13912345678 |
NF |
4 |
$NF |
13912345678 |
4.2 打印指定列
打印第 1 列和第 2 列:
bash
[root@atguigu ~]# awk '{print $1,$2}' test.txt
张伟 28
李芳 34
王强 42
赵敏 29
刘洋 36
陈静 31
李明 25
吴丽 27
钱伟 39
孙芳 33
打印第 1、2、3、4 列,并添加表头:
bash
[root@atguigu ~]# awk 'BEGIN{print "name age gender tel"} {print $1,$2,$3,$4}' test.txt
name age gender tel
张伟 28 男 13912345678
李芳 34 女 13887654321
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
陈静 31 女 13487654321
李明 25 男 13312345678
吴丽 27 女 13287654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
如果只想看手机号:
bash
awk '{print $4}' test.txt
或者使用最后一列:
bash
awk '{print $NF}' test.txt
当每一行字段数量固定时,$4 和 $NF 结果一样;但如果字段数量不固定,$NF 更适合取"最后一个字段"。
4.3 拼接固定格式输出
awk 不只能原样打印字段,还能把字段拼接成指定格式。
例如输出 name:xxx,age:xxx:
bash
[root@atguigu ~]# awk '{print "name:"$1",age:"$2}' test.txt
name:张伟,age:28
name:李芳,age:34
name:王强,age:42
name:赵敏,age:29
name:刘洋,age:36
name:陈静,age:31
name:李明,age:25
name:吴丽,age:27
name:钱伟,age:39
name:孙芳,age:33
注意这里的拼接方式:
bash
print "name:"$1",age:"$2
含义是:
"name:"是固定字符串;$1是第 1 列;",age:"是固定字符串;$2是第 2 列。
在 awk 中,字符串和变量挨在一起就会自动拼接。
4.4 使用 NF 获取最后一列
查询最后一列和倒数第二列:
bash
[root@atguigu ~]# awk '{print $NF,$(NF-1)}' test.txt
13912345678 男
13887654321 女
13712345678 男
13687654321 女
13512345678 男
13487654321 女
13312345678 男
13287654321 女
13112345678 男
13087654321 女
$(NF-1) 外层括号不能省略。因为 NF-1 是一个表达式,表示"字段数量减 1",再通过 $() 取这一列的值。
可以这样理解:
bash
$NF # 最后一列
$(NF-1) # 倒数第二列
$(NF-2) # 倒数第三列
4.5 使用 -F 指定分隔符
awk 默认使用空格或制表符分隔字段。如果文件变成逗号分隔:
bash
张伟,28,男,13912345678
李芳,34,女,13887654321
直接执行:
bash
awk '{print $1,$2}' test.csv
结果可能只输出整行,因为在 awk 看来,这一行没有空格,整行就是第 1 列。
正确写法是使用 -F 指定分隔符:
bash
[root@atguigu ~]# awk -F',' '{print $1,$2}' test.csv
张伟 28
李芳 34
王强 42
赵敏 29
刘洋 36
陈静 31
李明 25
吴丽 27
钱伟 39
孙芳 33
常见分隔符示例:
| 文件格式 | 分隔符 | 命令写法 |
|---|---|---|
| 空格分隔 | 默认 | awk '{print $1}' file |
| 逗号分隔 | , |
awk -F',' '{print $1}' file |
| 冒号分隔 | : |
awk -F':' '{print $1}' file |
| 竖线分隔 | ` | ` |
| 一个或多个空格、斜杠 | 正则 | awk -F'[ /]+' '{print $3}' file |
-F 后面也可以写正则表达式,这在处理命令输出时很常用。
五、awk 获取行

5.1 使用 NR 按行号过滤
NR 表示当前读取到第几行。
获取第 1 行:
bash
[root@atguigu ~]# awk 'NR==1' test.txt
张伟 28 男 13912345678
获取第 3 到第 5 行:
bash
[root@atguigu ~]# awk 'NR>=3&&NR<=5' test.txt
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
这里没有写 {print},因为 awk 默认动作就是打印整行。也就是说:
bash
awk 'NR==1' test.txt
等价于:
bash
awk 'NR==1 {print}' test.txt
5.2 使用比较运算符
awk 中常见比较运算符如下:
| 运算符 | 含义 | 示例 |
|---|---|---|
== |
等于 | NR==1 |
> |
大于 | $2>30 |
>= |
大于等于 | NR>=3 |
< |
小于 | $2<30 |
<= |
小于等于 | NR<=5 |
!= |
不等于 | $3!="男" |
例如查询年龄大于 30 的行:
bash
[root@atguigu ~]# awk '$2>30' test.txt
李芳 34 女 13887654321
王强 42 男 13712345678
刘洋 36 男 13512345678
陈静 31 女 13487654321
钱伟 39 男 13112345678
孙芳 33 女 13087654321
查询年龄小于等于 30 的行:
bash
[root@atguigu ~]# awk '$2<=30' test.txt
张伟 28 男 13912345678
赵敏 29 女 13687654321
李明 25 男 13312345678
吴丽 27 女 13287654321
5.3 使用逻辑运算符
多个条件可以使用逻辑运算符组合:
| 运算符 | 含义 | 示例 |
|---|---|---|
&& |
并且 | $2>30 && $3=="男" |
| ` | ` | |
! |
取反 | ! /男/ |
查询年龄大于 30 且性别为男:
bash
[root@atguigu ~]# awk '$2>30 && $3=="男"' test.txt
王强 42 男 13712345678
刘洋 36 男 13512345678
钱伟 39 男 13112345678
查询性别不是男的行:
bash
[root@atguigu ~]# awk '$3!="男"' test.txt
李芳 34 女 13887654321
赵敏 29 女 13687654321
陈静 31 女 13487654321
吴丽 27 女 13287654321
孙芳 33 女 13087654321
查询第 1 行到第 5 行里年龄大于 30 的行:
bash
awk 'NR>=1 && NR<=5 && $2>30' test.txt
这种写法在实际过滤日志、命令输出时非常常见。
5.4 使用正则匹配行
awk 可以直接使用 /正则/ 过滤整行。
查询包含"男"的行:
bash
[root@atguigu ~]# awk '/男/' test.txt
张伟 28 男 13912345678
王强 42 男 13712345678
刘洋 36 男 13512345678
李明 25 男 13312345678
钱伟 39 男 13112345678
查询包含"张"的行:
bash
[root@atguigu ~]# awk '/张/' test.txt
张伟 28 男 13912345678
查询空行:
bash
awk '/^$/' file
如果只想查询非空行,可以取反:
bash
awk '!/^$/' file
5.5 使用字段条件过滤行
整行正则匹配是看整行内容,有时会误匹配。更准确的方式是指定某一列。
查询第 1 列包含"张"的行:
bash
[root@atguigu ~]# awk '$1~/张/' test.txt
张伟 28 男 13912345678
查询第 3 列等于"男"的行:
bash
[root@atguigu ~]# awk '$3=="男"' test.txt
张伟 28 男 13912345678
王强 42 男 13712345678
刘洋 36 男 13512345678
李明 25 男 13312345678
钱伟 39 男 13112345678
查询第 3 列不是"男"的行:
bash
awk '$3!="男"' test.txt
查询第 2 列大于 30 的行:
bash
awk '$2>30' test.txt
字段正则匹配常用两个符号:
| 写法 | 含义 |
|---|---|
$1~/张/ |
第 1 列匹配正则 张 |
$1!~/张/ |
第 1 列不匹配正则 张 |
5.6 使用范围条件取连续内容
awk '条件1,条件2' 可以获取从条件 1 匹配行到条件 2 匹配行之间的内容,包括开始行和结束行。
例如:
bash
[root@atguigu ~]# seq 10 | awk '/3/,/5/'
3
4
5
也可以按行号取范围:
bash
[root@atguigu ~]# awk 'NR==3,NR==5' test.txt
王强 42 男 13712345678
赵敏 29 女 13687654321
刘洋 36 男 13512345678
这种写法适合截取日志中的一段内容,比如从某个开始标记到某个结束标记。
需要注意,范围条件不是"只判断一次"。如果后面再次遇到条件 1,还会再次开启范围匹配。
六、awk 取行取列实操
6.1 提取网卡 IP 地址
实际运维里,awk 经常和其他命令通过管道组合使用。
查看网卡信息:
bash
[root@atguigu ~]# ip a s ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:9d:02:d5 brd ff:ff:ff:ff:ff:ff
inet 192.168.88.111/24 brd 192.168.88.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::5f08:a955:5a42:b85a/64 scope link noprefixroute
valid_lft forever preferred_lft forever
目标是提取 IPv4 地址 192.168.88.111。
可以先取第 3 行,再按空格和 / 分隔:
bash
[root@atguigu ~]# ip a s ens33 | awk 'NR==3' | awk -F '[ /]+' '{print $3}'
192.168.88.111
也可以合并成一条 awk:
bash
[root@atguigu ~]# ip a s ens33 | awk -F '[ /]+' 'NR==3 {print $3}'
192.168.88.111
这里的分隔符是:
bash
-F '[ /]+'
含义是:用一个或多个空格或 / 作为分隔符。
第 3 行内容大概是:
bash
inet 192.168.88.111/24 brd 192.168.88.255 scope global noprefixroute ens33
按 [ /]+ 分隔后:
| 字段 | 内容 |
|---|---|
$1 |
空字段或 inet,取决于前导空格处理细节 |
$2 |
inet 或 192.168.88.111 |
$3 |
192.168.88.111 |
$4 |
24 |
不同系统输出格式可能略有差异。单纯获取本机 IP 时,更直接的命令是:
bash
hostname -I
但这个例子很适合用来理解 awk 的行列组合能力。
6.2 提取内存信息
查看内存:
bash
[root@atguigu ~]# free
total used free shared buff/cache available
Mem: 2027896 220472 1685724 9748 121700 1666284
Swap: 2097148 0 2097148
如果要取 Swap 的 used 值,也就是第 3 行第 3 列:
bash
[root@atguigu ~]# free | awk 'NR==3 {print $3}'
0
如果要计算内存可用容量百分比:
bash
[root@atguigu ~]# free | awk 'NR==2 {print $7/$2*"100%"}'
82.1671%
这里:
NR==2表示只处理Mem:这一行;$2是总内存;$7是 available;$7/$2得到可用比例;- 乘以
"100%"时,awk会把前面的计算结果乘以100,再拼上%的显示效果。
更推荐写得清楚一点:
bash
free | awk 'NR==2 {print ($7/$2)*100"%"}'
如果希望保留两位小数,可以使用 printf:
bash
free | awk 'NR==2 {printf "available: %.2f%%\n", ($7/$2)*100}'
6.3 过滤 /etc/passwd 指定字段
/etc/passwd 是冒号分隔文件,字段含义大致如下:
bash
用户名:密码占位符:UID:GID:注释:家目录:登录 Shell
查看第 1、3、4 列:
bash
awk -F':' '{print $1,$3,$4}' /etc/passwd
查询第 3 列以 0 或 1 开头的行,并输出第 1、3、4 列:
bash
[root@atguigu ~]# awk -F':' '$3~/^[01]/ {print $1,$3,$4}' /etc/passwd
查询第 3 列大于 100 的行,并输出第 1、3、最后一列:
bash
[root@atguigu ~]# awk -F':' '$3>100 {print $1,$3,$NF}' /etc/passwd
这里把行过滤和列输出结合起来了:
bash
条件 {动作}
对应到这条命令就是:
bash
$3>100 {print $1,$3,$NF}
意思是:如果第 3 列大于 100,就输出第 1 列、第 3 列和最后一列。
七、awk 统计计算

7.1 统计行数
统计 test.txt 行数:
bash
[root@atguigu ~]# awk '{i=i+1} END{print i}' test.txt
10
这条命令可以拆开理解:
bash
{i=i+1}
每读取一行,就让变量 i 加 1。
bash
END{print i}
文件读完后,打印变量 i。
awk 中变量没有提前赋值时,参与数字计算会按 0 处理,所以第一行读取时相当于:
bash
i=0+1
更常见的简写是:
bash
awk '{i++} END{print i}' test.txt
其实统计行数也可以直接打印 NR:
bash
awk 'END{print NR}' test.txt
因为读取到最后一行时,NR 正好就是总行数。
7.2 统计空行数量
统计 /etc/services 中空行数量:
bash
[root@atguigu ~]# awk '/^$/' /etc/services | wc -l
17
也可以直接在 awk 内部计数:
bash
[root@atguigu ~]# awk '/^$/ {i++} END{print i}' /etc/services
17
两种方式都能实现:
| 写法 | 思路 |
|---|---|
| `awk '/^$/' file | wc -l` |
awk '/^$/ {i++} END{print i}' file |
awk 自己筛选并计数 |
第二种写法更能体现 awk 的统计能力。
7.3 对字段求和
对 test.csv 中年龄求和:
bash
[root@atguigu ~]# awk -F',' '{sum=sum+$2} END{print sum}' test.csv
324
可以简写成:
bash
awk -F',' '{sum+=$2} END{print sum}' test.csv
执行过程如下:
| 读取行 | $2 |
sum |
|---|---|---|
| 张伟 | 28 | 28 |
| 李芳 | 34 | 62 |
| 王强 | 42 | 104 |
| ... | ... | ... |
| 孙芳 | 33 | 324 |
最后在 END 中输出 sum。
如果文件是空格分隔的 test.txt,可以不写 -F',':
bash
awk '{sum+=$2} END{print sum}' test.txt
7.4 分组计数与分组求和
统计男生年龄总和:
bash
[root@atguigu ~]# awk '$3=="男" {sum+=$2} END{print sum}' test.txt
170
统计男生数量:
bash
[root@atguigu ~]# awk '$3=="男" {count++} END{print count}' test.txt
5
统计女生年龄总和:
bash
[root@atguigu ~]# awk '$3=="女" {sum+=$2} END{print sum}' test.txt
154
统计女生数量:
bash
[root@atguigu ~]# awk '$3=="女" {count++} END{print count}' test.txt
5
如果想一次性统计男女数量,可以用数组:
bash
[root@atguigu ~]# awk '{count[$3]++} END{for(i in count) print i,count[i]}' test.txt
男 5
女 5
数组先不用想得太复杂。这里可以理解为:
count["男"]++:男出现一次就加 1;count["女"]++:女出现一次就加 1;END中遍历数组,把每个类别和数量打印出来。
7.5 计算平均值
计算所有人的平均年龄:
bash
[root@atguigu ~]# awk '{sum+=$2;count++} END{print sum/count}' test.txt
32.4
保留两位小数:
bash
[root@atguigu ~]# awk '{sum+=$2;count++} END{printf "%.2f\n",sum/count}' test.txt
32.40
计算男生平均年龄:
bash
[root@atguigu ~]# awk '$3=="男" {sum+=$2;count++} END{print sum/count}' test.txt
34
计算女生平均年龄:
bash
[root@atguigu ~]# awk '$3=="女" {sum+=$2;count++} END{print sum/count}' test.txt
30.8
为了避免没有匹配数据时除以 0,可以写得更稳一点:
bash
awk '$3=="男" {sum+=$2;count++} END{if(count>0) print sum/count; else print 0}' test.txt
7.6 计算内存可用率
使用 free 计算内存 available 百分比:
bash
[root@atguigu ~]# free | awk 'NR==2 {printf "available: %.2f%%\n", ($7/$2)*100}'
available: 82.17%
如果要输出已用率,可以使用 used / total:
bash
free | awk 'NR==2 {printf "used: %.2f%%\n", ($3/$2)*100}'
也可以同时输出两项:
bash
free | awk 'NR==2 {printf "used: %.2f%%, available: %.2f%%\n", ($3/$2)*100, ($7/$2)*100}'
这种写法经常用于脚本巡检。相比手动看 free 输出,直接算出百分比更适合告警判断。
7.7 查找最大值和最小值
输出年龄最大的人:
bash
[root@atguigu ~]# awk '$2>max {max=$2;name=$1} END{print "name:"name",age:"max}' test.txt
name:王强,age:42
这条命令的逻辑是:
- 每读取一行,就拿
$2和当前max比较; - 如果
$2更大,就更新max和name; - 读完后输出最终保存的姓名和最大年龄。
输出年龄最小的人,可以这样写:
bash
[root@atguigu ~]# awk 'NR==1 || $2<min {min=$2;name=$1} END{print "name:"name",age:"min}' test.txt
name:李明,age:25
这里用 NR==1 初始化最小值。否则 min 默认按 0 处理,所有年龄都大于 0,就无法正确更新最小值。
输出年龄最大的男生:
bash
[root@atguigu ~]# awk '$3=="男" && $2>max {max=$2;name=$1} END{print "name:"name",age:"max}' test.txt
name:王强,age:42
输出年龄最小的男生:
bash
[root@atguigu ~]# awk '$3=="男" && (count==0 || $2<min) {min=$2;name=$1;count++} END{print "name:"name",age:"min}' test.txt
name:李明,age:25
输出年龄最大的女生:
bash
[root@atguigu ~]# awk '$3=="女" && $2>max {max=$2;name=$1} END{print "name:"name",age:"max}' test.txt
name:李芳,age:34
输出年龄最小的女生:
bash
[root@atguigu ~]# awk '$3=="女" && (count==0 || $2<min) {min=$2;name=$1;count++} END{print "name:"name",age:"min}' test.txt
name:吴丽,age:27
最大值一般可以直接从默认 0 开始比较;最小值建议用第一条有效记录初始化,这样不容易出错。
八、常见运维场景

场景一:查看磁盘使用率超过阈值的分区
bash
df -Th | awk -F '[ %]+' 'NR>1 && $6>=10'
含义:
df -Th查看文件系统类型和容量;-F '[ %]+'使用空格和%作为分隔符;NR>1跳过表头;$6>=10过滤使用率大于等于 10 的行。
生产环境中阈值通常不是 10,而是 80、85、90 这类值:
bash
df -Th | awk -F '[ %]+' 'NR>1 && $6>=85 {print $1,$6"%",$NF}'
场景二:提取进程列表中的指定字段
查看进程时只输出用户、PID、CPU、内存和命令:
bash
ps aux | awk 'NR==1 {print $1,$2,$3,$4,$11} NR>1 {print $1,$2,$3,$4,$11}'
如果只看 CPU 使用率大于 10 的进程:
bash
ps aux | awk 'NR>1 && $3>10 {print $1,$2,$3,$11}'
场景三:统计某类日志数量
统计日志中包含 ERROR 的行数:
bash
awk '/ERROR/ {count++} END{print count}' app.log
统计每种状态码出现次数,假设状态码在第 9 列:
bash
awk '{code[$9]++} END{for(i in code) print i,code[i]}' access.log
如果要排序,可以交给 sort:
bash
awk '{code[$9]++} END{for(i in code) print i,code[i]}' access.log | sort -k2 -nr
场景四:从配置文件中提取有效配置
过滤空行和注释行:
bash
awk '!/^#/ && !/^$/' nginx.conf
如果配置文件前面有空格,可以写得更稳一点:
bash
awk '!/^[[:space:]]*#/ && !/^[[:space:]]*$/' nginx.conf
这个命令经常用于快速查看"真正生效的配置内容"。
九、复习检查点
可以用下面这些问题检查自己是否掌握了本篇重点:
awk '{print $1,$2}' test.txt中$1和$2分别表示什么?$0、NF、$NF、NR的区别是什么?- 为什么逗号分隔文件要使用
awk -F','? BEGIN{}和END{}分别在什么时候执行?awk '$2>30' test.txt为什么可以筛出年龄大于 30 的行?$1~/张/和/张/有什么区别?awk 'NR>=3&&NR<=5' test.txt和awk 'NR==3,NR==5' test.txt有什么相似点?- 统计某列总和时,为什么要在主体中累加,在
END中输出? - 求最小值时,为什么不能简单依赖变量默认值 0?
df -Th | awk -F '[ %]+' 'NR>1 && $6>=85'这条命令每一部分分别是什么意思?
总结
awk 的核心不是命令有多长,而是处理思路很清晰:
- 一行一行读取文本;
- 按分隔符切成多个字段;
- 用
NR、正则或字段条件筛选目标行; - 用
$1、$2、$NF等字段变量取出目标列; - 需要统计时,用变量在主体中累加,在
END中输出结果。
只要抓住"行"和"列"这两个维度,awk 就不难理解。
常用口诀可以记成:
NR管行号,NF管列数;$1取第一列,$NF取最后列;-F定分隔符,END出统计结果。
实际运维中,awk 很少单独存在,更多时候是和管道一起使用:
bash
df -Th | awk ...
free | awk ...
ip a | awk ...
ps aux | awk ...
cat file | awk ...
它的价值就在这里:前面的命令负责产生文本,awk 负责把文本整理成我们真正需要的数据。
十、注意事项
awk默认分隔符是空格和制表符,处理逗号、冒号、竖线分隔文件时要使用-F。$0表示整行,$1表示第 1 列,NF表示字段数量,$NF表示最后一列,不要混用。- 输出中文、英文等字符串时要加引号,数字可以不加引号。
- 字段比较字符串时也要加引号,例如
$3=="男"。 - 正则匹配整行用
/正则/,匹配指定字段用$字段~/正则/。 - 管道输出格式可能因系统版本不同略有差异,写脚本前要先确认字段位置。
- 求最小值时要先初始化,常见写法是
NR==1 || $2<min,或者使用计数变量判断第一条有效记录。 - 统计结果建议在
END{}中输出,否则每读取一行都会输出一次中间结果。 - 如果数据中字段可能包含空格,例如 CSV 中的带引号字段,普通
awk -F','不能完整处理复杂 CSV,需要使用专门工具或脚本解析。 - 运维脚本中使用
awk做阈值判断时,要明确单位和字段位置,避免把表头、百分号或不同命令格式当成普通数据处理。