一条命令带你了解awk高阶用法
前言
优化shell脚本,主要是通过shell获取主机资源使用率,其中磁盘I/O繁忙使用了iostat
命令取最后一列,也就是util列。
一、原始命令分析
iostat -k -x 1 1 | sed -n '/Device/,/Device/!p' | sed '1,5d' | egrep -v "^$"
其实单看这条命令没有什么问题,但是iostat
命令取第一次的值通常是主机启动到目前的均值,不会很直观反应当前的状态,所以打算取2次,取第2次的值。
另外,过度依赖sed和grep对特定输出格式进行解析的脚本是非常脆弱的,所以打算修改获取方式,使用更强大工具——awk
二、awk实现
使用awk命令,如下:
iostat -k -x 1 2 | awk '/^Device/ {count++} count==2 && !/^Device/'
命令解析
-
/Device/ {count++}
:这是一个模式动作对。当 awk 读到包含 “Device” 的行时,它就将一个我们自己定义的变量 count 的值加 1。 -
count==2
:这是一个模式,不带动作。当 count 的值等于 2 时(即我们已经进入了第二个统计块),这个条件就为真。awk 的默认动作是打印整行 (print $0)。 -
count==2 && !/Device/
:最后又增加了一个条件 !/Device/,意思是“并且不包含 "Device"开头的行”。这样就巧妙地跳过了第二个统计块的表头行,只打印纯粹的数据行。
三、高阶用法
除了常用的'{print $0,$NF,$NR}'
打印某一列,-F
参数指定分隔符外,awk还有很多便捷易用的功能。详细的用法可以参考这篇文章,文本处理三剑客awk
awk 工作模式
awk的它的核心结构
pattern { action }
awk 会逐行读取输入内容,对于每一行,它都会:
-
用 pattern (模式) 去匹配。
-
如果当前行匹配 pattern,awk 就执行 action (动作) 中定义的命令。
这个结构有几个重要的变种:
-
只有 { action }: 如果没有 pattern,那么 action 会对每一行都执行。
-
只有 pattern: 如果一个 pattern 后面没有 { action },awk 会执行一个默认动作,即打印整行内容 (print $0)。
-
多个 pattern { action }: 您可以在一个 awk 命令中写多组成对的 pattern { action },awk 会对每一行依次检查所有模式。
awk模式
1. 正则表达式模式 (Regular Expression Pattern)
使用斜杠 /
包裹。
- 语法:
/正则表达式/
- 作用: 对当前处理的行进行正则表达式匹配。如果匹配成功,则执行后续的
{ action }
。 - 示例:
# 打印所有包含 "error" 字符串的行 awk '/error/' /var/log/messages
2. 关系/条件表达式模式 (Relational/Conditional Expression Pattern)
直接使用编程语言中的逻辑判断。
- 语法:
expression
(例如NF > 3
,$1 == "user"
,my_var == 1
) - 作用: 计算表达式的值。如果值为“真”(即非零或非空字符串),则执行后续的
{ action }
。 - 示例:
# 打印 /etc/passwd 文件中,用户ID(第三个字段)大于1000的行 awk -F: '$3 > 1000' /etc/passwd
3. 范围模式 (Range Pattern)
这是一种非常强大的模式,用于处理跨越多行的文本块。
- 语法:
pattern1, pattern2
- 作用: 这个模式会匹配一个范围内的所有行。它从第一个匹配
pattern1
的行开始,到下一个匹配pattern2
的行结束(包括这两行)。 - 示例:
# 打印日志文件中,从包含 "START TRANSACTION" 的行开始,到包含 "END TRANSACTION" 的行结束之间的所有内容 awk '/START TRANSACTION/,/END TRANSACTION/' app.log
4. 特殊模式 BEGIN
和 END
这是 awk
的两个“钩子”(hook),它们不匹配任何输入行,而是在特定的时间点执行。
-
BEGIN
:- 语法:
BEGIN { action }
- 作用:
BEGIN
块中的action
会在awk
开始处理任何输入行之前执行,并且只执行一次。 - 用途: 非常适合用来初始化变量、打印报表表头等。
- 语法:
-
END
:- 语法:
END { action }
- 作用:
END
块中的action
会在awk
处理完所有输入行之后执行,并且只执行一次。 - 用途: 非常适合用来进行最终的计算、打印总计和报表结尾。
- 语法:
-
示例:
# 计算文件中所有数字的总和 echo -e "10\n20\n30" | awk ' BEGIN { sum=0; print "开始计算..." } { sum += $1 } END { print "计算完成,总和为:", sum }'
输出:
开始计算... 计算完成,总和为: 60
5. 组合模式 (Compound Pattern)
通过逻辑运算符将上述各种模式组合起来,形成更复杂的匹配条件。
- 语法:
pattern1 && pattern2
(逻辑与 AND)pattern1 || pattern2
(逻辑或 OR)! pattern
(逻辑非 NOT)
- 作用: 创建复合逻辑判断。
- 示例:
# 打印第三个字段大于100,并且行中包含 "admin" 的行 awk -F: '$3 > 100 && /admin/' /etc/passwd
四、总结
最后大家应该理解到awk处理文本的强大,它内置的变量、逻辑判断和模式匹配能力,使其能够用非常少的代码替代复杂的命令链,处理结构化的文本数据。
就像在我本次修改的脚本中,获取util最高的设备,使用awk一条命令就可以实现,因为awk本身自带循环能力(逐行读取),再加上可以使用判断、变量不需要定义直接使用等特点,让逻辑代码变得非常简洁。而且使用awk命令看起来比使用一堆管道符加grep
、head
等命令高级。
iostat -k -x 1 2 | awk '
# 找到第三个 "Device" 块 (即第二次的实时采样)
/^Device/ {count++}
count==2 && !/^Device/ {
# 如果当前行的最后一个字段 ($NF) 大于我们记录的 max 值
# 就更新 max 的值为当前行的最后一个字段
if ($NF > max) {
max = $NF
}
}
END {
# 所有行处理完毕后,打印最终记录的 max 值
# 如果没有设备数据,max 默认为0,打印出来也是0
print max
}
'