awk命令与Zabbix自定义参数的介绍

awk 命令

awk是一种处理文本文件的语言,是一个强大的文本分析公具。
awk处理文本和数据的方式:逐行读入文本,寻找匹配特定模式的行,然后进行操作。

我们提供的服务有:网站制作、网站建设、微信公众号开发、网站优化、网站认证、临川ssl等。为1000+企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的临川网站制作公司

输出文件匹配行的特定字段

功能很强大,所以有很多用处。这里我主要关注下面这样的场景:
逐行读入文本,按规则匹配特定的行,以空格为默认分隔符将每行切片,输出其中特定的某个切片(切开的部分可以进行各种分析处理,这里就是要输出其中以段):

$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
$ awk '/local/ {print $1}' /etc/hosts
127.0.0.1
::1
$ 

这种方法很适合用来做zabbix的自定义key的监控。比如从free命令中,提取出内存的使用量:

$ free
              total        used        free      shared  buff/cache   available
Mem:        1855432      320688     1238808       10612      295936     1495432
Swap:       2093052           0     2093052
$ free | awk '/^Mem:/ {print $3}'
320688
$ 

grep命令 
同样的效果,也可以通过grep命令来把需要的行过滤出来,然后还得借助cut命令来进行列切割。  
但是使用awk的话就一步搞定了。

内置变量

内置变量先列出来,后面会用到其中一些。
awk内置变量:

  • ARGC: 命令行参数个数
  • ARGV: 命令行参数排列
  • ENVIRON: 支持队列中系统环境变量的使用
  • FILENAME: awk浏览的文件名
  • FNR: 浏览文件的记录数
  • FS: 设置输入域分隔符,等价于命令行 -F选项
  • IGNORECASE: 如果把这个变量设为1,则正则表达式忽略大小写
  • NF: 浏览记录的域的个数
  • NR: 已读的记录数
  • OFS: 输出域分隔符
  • ORS: 输出记录分隔符
  • RS: 控制记录分隔符
  • $0: 变量是指整条记录
  • $1: 表示当前行的第一个域,$2表示当前行的第二个域,......以此类推
  • $NF: 既然 NF是列的总数,那么$NF就是最后一列的值
  • $NF-1: 这个是倒数第二列,其他以此类推

上面这些变量,有些是直接来使用的。比如$1,$NF,后面的例子中会用到,也比较好理解。  
还有些是用来改变awk行为的,需要对变量进行设置,这个需要会为变量赋值,有多种方式可以实现。
比如FS,是用来指定分隔符的,默认的分隔符是空白符,但是可以指定。这就需要自己定义FS的值。不过分隔符还提供了一个 -F 选项来定义。所以也可以在命令行选项中设置。
但是其他一些变量需要指定,但又没有提供别的方法的话,就只能用过为变量赋值来实现了。
分隔符和为变量赋值的方式在后面会展开,为变量赋值参考自定义变量的内容。

分隔符

默认awk是以空白符来做分隔的。使用 -F 选项可以自定义分隔符:

$ grep -e "^root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
$ awk -F: '/^root/ {print $1,$NF}' /etc/passwd
root /bin/bash
$ 

这里将分隔符指定为冒号。

多分隔符 
默认的也是多分隔符的情况,空格、制表符等都会被识别。自己要指定多个分隔符,则是用中括号把需要识别的分隔符都括起来:

$ echo "a-b_c=d-E_F=G" | awk -F[-_=] '{print $1,$2,$3,$4,$5,$6,$7}'
a b c d E F G
$ 

过滤连续的分隔符 
-F 选项也是支持正则表达式的,中括号就是正则表达式字符集合的意思。但是如果这时遇到连续的分隔符,就会有问题。下面使用逗号和空格作为分隔符,并且每次都连续出现:

$ echo "a,,b  c" | awk -F'[ ,]' '{print $1"-"$2"-"$3}' 
a--b
$ 

正则表达式中匹配一次或多次,使用加号后,就可以了:

$ echo "a,,b  c" | awk -F'[ ,]+' '{print $1"-"$2"-"$3}'
a-b-c
$ 

特殊字符分隔符 
特殊字符应该就是这些: $、^、*、(、)、[、]、?、.、|
单独作为分隔符并没有问题:

$ echo '1a$1b$1c' | awk -F'$' '{print $1"-"$2"-"$3}'   
1a-1b-1c
$ 

如果指定多个字符作为一个整体作为一个分隔符,就会有问题,需要转义。比如这里要将 $1 作为分隔符:

$ echo '1a$1b$1c' | awk -F'$1' '{print $1"-"$2"-"$3}'
1a$1b$1c--
$ echo '1a$1b$1c' | awk -F'\\$1' '{print $1"-"$2"-"$3}'
1a-b-c
$ 

再来个多个特殊字符组合的:

$ echo 'a$|b$|c' | awk -F'\\$\\|' '{print $1"-"$2"-"$3}'
a-b-c
$ 

默认分隔符 
默认就是空白符作为分隔符,并且能够识别连续的空白符。默认分隔符就是下面的这个正则表达式:

FS="[[:space:]+]" 

看上面的内置变量,FS和-F选项是等价的。

格式化输出printf

除了用print,还可以用printf做格式化输出。这里就给出一个例子,关于printf格式化输出,需要的话再去参考下C语言的printf的功能把。

一般都用print输出:

$ awk -F: '{print "filename:" FILENAME ",linenumber:" NR ",columns:" NF ",linecontent:"$0}' /etc/passwd     
filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash
filename:/etc/passwd,linenumber:2,columns:7,linecontent:bin:x:1:1:bin:/bin:/sbin/nologin
filename:/etc/passwd,linenumber:3,columns:7,linecontent:daemon:x:2:2:daemon:/sbin:/sbin/nologin

对比下用printf格式化输出后的效果:

$ awk -F: '{printf ("filename:%10s, linenumber:%3s,column:%3s,content:%3f\n",FILENAME,NR,NF,$0)}' /etc/passwd
filename:/etc/passwd, linenumber:  1,column:  7,content:0.000000
filename:/etc/passwd, linenumber:  2,column:  7,content:0.000000
filename:/etc/passwd, linenumber:  3,column:  7,content:0.000000

BEGIN 和 END 模块

通常,对于每个输入行,awk 都会执行一次脚本代码块。  
有时,需要在 awk 开始处理输入文件中的文本之前执行初始化代码。这就需要定义一个 BEGIN 块。
另外,还有一个 END 块,用于执行最终计算或打印应该出现在输出流结尾的摘要信息。

在BEGIN块中定义内置变量 
这里在BEGIN块中定义了两个内置变量:

$ echo "a,,b   c" | awk 'BEGIN{FS="[ ,]+";OFS="-"}{print $1,$2,$3}' 
a-b-c
$ 

FS是分隔符,OFS是输出字段分隔符。在之前的例子中,不用BEGIN块也是能实现这个效果的。  
这里修改的是一个内置变量,但是方法是针对变量的,包括自定义变量。具体参考下一章“awk自定义变量”。

这里主要挑BEGIN块举例用法。END块可以实现计算统计输出的功能,暂时用不上,略过。

awk自定义变量

除了内置变量,也可以定义自定义变量并使用。这部分内容对于灵活的配置非常有用,而且如果自己写,也会遇到一些坑。

定义在后面

这里变量的赋值在发生在BEGIN块执行之后的
直接写在后面:

$ echo | awk '{print key1,key2}' key1=v1 key2=V2
v1 V2
$ 

这种用法在BEGIN块中是识别不了变量的。BEGIN块的执行在这些变量定义之前。不过还有其他的方法可以用。
另外,这里使用管道作为标准输入。如果是从文件输入的话,文件路径在写最后。

在BEGIN块中定义

这里变量额赋值是在BEGIN块执行的时候
在BEGIN块中可以对内置变量赋值,同样的也可以为自定义变量赋值

$ echo | awk 'BEGIN{key1="v1";key2="value2";OFS="_"}{print key1,key2}' 
v1_value2
$ 

使用 -v 选项

这里变量的赋值是在BEGIN块执行之前
这个方法在发生在BEGIN块执行之前的:

$ echo | awk -v key1=V1 -v key2=value2 '{print key1,key2}'
V1 value2
$ 

如果是多个变量,则使用 -v 多次。

如果把上面两个方法合起来:

$ echo | awk -v key1=v1 -v key2=v2 'BEGIN{print "BEGIN: "key1,key2}{print "ACTION: "key1,key2}' key1=VALUE1 key2=VALUE2      
BEGIN: v1 v2
ACTION: VALUE1 VALUE2
$  

先是 -v 进行赋值,然后BEGIN块执行。之后是最后的变量赋值,如果有同名的就替换值,之后再逐行执行。打印出来的就是之后改变的值。

打印环境变量

最好的方法在后一小节。这里的方法也是可行的,但是可读性不好。
写这段是为了理解一下命令参数解析的过程,以及一些特殊情况的处理。
要直接打印环境变量是这样的:

$ echo | awk  '{print "'"$PATH"'"}'
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
$ 

这里的显示不明显,两边都是一对双引号套一个单引号,看图

awk命令与Zabbix自定义参数的介绍

解释说明 
先用一个简单点的环境变量来举例:

$ echo $USER
root
$ 

这个没有什么空格换特殊字符,这样可以去掉最里面的一对双引号:

$ echo | awk  '{print "'$USER'"}'  
root
$ 

这里成对出现了2对单引号,所以就被分成了这样两个部分:awk  '{print'"}'。awk对2个单引号内的命令起作用。
剩下的就是  $USER 了,这个最早就被 shell 给处理替换了。  
在变量本身被shell处理完之后,如果有空格之类的,有会被认为不是一个部分。这里就再用双引号把环境变量的值包起来,将值作为整体的一个域。

我的理解 
最外层的引号是用来界定字符边界的,但是只要是连续的就被系统认为是一串(一个域)。可以用多对引号把多个字符串引起来,但是每对引号之间不要出现分隔符。这样,最后解析交给命令处理的还是一个整体的字符串(一个域)。  
下面是用echo命令的演示:

$ echo 'abc''def'
abcdef
$ echo 'abc'$USER'def'
abcrootdef
$ echo 'abc '$USER' def'
abc root def
$ 

加上for循环再演示一次:

$ HELLO='Hello World !'
$ for i in $HELLO; do echo $i;done
Hello
World
!
$ for i in 'BEFOR'$HELLO'AFTER'; do echo $i;done
BEFORHello
World
!AFTER
$ for i in 'BEFOR'"$HELLO"'AFTER'; do echo $i;done
BEFORHello World !AFTER
$ 

最外层的引号仅仅是界定边界的,用多对引号但是所有内容都相连,也被认为是一个域。

虽然有多对引号,但是所有内容都是相连的,没有分隔符,最后交给命令处理的还是一个域。
这样做的好处就是,用了单引号,但是把需要shell解析的部分放到了单引号的外面,这样shell还是可以正常解析。
为了保证环境变量解析完之后依然是一个域,需要用双引号引起来。
再来就是awk中接着print的双引号了。awk中的引号不是界定边界的而是区分是变量还是字符串的。没有双引号的话表示这个内容是变量,用双引号引起来表示里面的内容是字符串,直接打印。

其他写法 
下面两种写法也能实现同样的效果,帮助理解吧:

$ echo | awk "{print \"$PATH\"}"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
$ echo } awk \{print\""$PATH"\"\}
} awk {print"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin"}
$ 

awk命令还是尽量用单引号引起来,防止shell对其中内容进行解释。就是第一种办法就最好的。

引用命令行定义的变量

最开始的3种方法,有2种是在引号外完成变量定义的,这样就不会对shell进行干扰:

$ echo | awk '{print path}' path="$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
$ echo | awk -v path="$PATH" '{print path}'                                            
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
$ 

先是用命令行的方式把环境变量赋值给自定义变量,这个操作在引号外。然后再引号里面直接用自定义变量就好了。  
如果是在BEGIN块中要这么做,就参考上以小节的做法。

awk 进阶

从free命令同获取当前内存使用数值:

$ free | awk '/^Mem:/ {print $3}'
335840
$ 

这里用的是正则匹配。不过awk还有其他的一些语法,可以做到更加精确的匹配,

条件限制

限制第一个字段值来匹配:

$ free | awk '$1 == "Mem:" {print $3}'
335744
$

限制要第几行的数据:

$ free | awk 'NR == 2 {print $3}'        
335796
$ 

条件语句

awk 也提供了 if, else, while 等这些条件语句,不过似乎用不了那么深,举一个if的例子。
同样是限制第几行,这里通过if语句来判断:

$ free | awk '{if(NR == 2) print $3}'             
335740
$ 

正则匹配

~是匹配正则表达式的运算符。另外,~!是不匹配正则表达式的运算符。
匹配第一个字段:

$ free | awk '$1 ~ /Mem/ {print $3}'
335844
$ 

关于正则还有一个内置变量是 IGNORECASE,如果设置为1,可以忽略大小写:

$ free | awk '$1 ~ "mem"  {print $3}' IGNORECASE=1
335708
$ 

为变量赋值的方法之前讲过了,有好几种方式。

打印九九乘法表

这个很高端的样子,就贴在最后了:

$ seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'
1x1=1
1x2=2   2x2=4
1x3=3   2x3=6   3x3=9
1x4=4   2x4=8   3x4=12  4x4=16
1x5=5   2x5=10  3x5=15  4x5=20  5x5=25
1x6=6   2x6=12  3x6=18  4x6=24  5x6=30  6x6=36
1x7=7   2x7=14  3x7=21  4x7=28  5x7=35  6x7=42  7x7=49
1x8=8   2x8=16  3x8=24  4x8=32  5x8=40  6x8=48  7x8=56  8x8=64
1x9=9   2x9=18  3x9=27  4x9=36  5x9=45  6x9=54  7x9=63  8x9=72  9x9=81
$ 

Zabbix 自定义参数(监控项)

用户自定义参数可以通过Zabbix agent执行非Zabbix原生的agent监控项。只要你有办法能通过命令获取到要监控的指标。

配置文件

可以直接在配置文件 zabbix_agentd.conf 中定义 UserParameter。

### Option: HostnameItem
#       Item used for generating Hostname if it is undefined. Ignored if Hostname is defined.
#       Does not support UserParameters or aliases.
#
# Mandatory: no
# Default:
# HostnameItem=system.hostname

Include配置
虽然直接写在这下面就可以了,不过配置文件还有一个Include的配置:

### Option: Include
#       You may include individual files or all files in a directory in the configuration file.
#       Installing Zabbix will create include directory in /usr/local/etc, unless modified during the compile time.
#
# Mandatory: no
# Default:
# Include=

Include=/etc/zabbix/zabbix_agentd.d/*.conf

# Include=/usr/local/etc/zabbix_agentd.userparams.conf
# Include=/usr/local/etc/zabbix_agentd.conf.d/
# Include=/usr/local/etc/zabbix_agentd.conf.d/*.conf

建议把这些配置分下类,创建独立的 zabbix_agentd.d/*.conf 文件,方便管理。

语法

自定义参数的语法如下:

UserParameter=,

key,就是监控项用的key。必须全局唯一。
命名要求:只能使用字母、数字、下划线、中横杠、点号。即 0-9a-zA-Z_-. 这些字符。

比如下面的文件中定义了3个通过free命令获取值的监控项:

$ cat /etc/zabbix/zabbix_agentd.d/os.conf
UserParameter=os.memory.total, free -m | awk '$1=="Mem:" {print $2}'
UserParameter=os.memory.used, free -m | awk '$1=="Mem:" {print $3}'
UserParameter=os.memory.free, free -m | awk '$1=="Mem:" {print $4}'

具体一步步如何实现的,参考下一小节。

调试步骤

自定义参数,一步步实现的操作过程。

第一步:写一个命令或脚本
能够成功的在命令行中把值打印出来:

$ free -m | awk '$1=="Mem:" {print $3}' 
569
$ 

由于zabbix是使用zabbix账号执行的,有些命令有可能zabbix无权限。所以可以加上sudo指定zabbix用户执行再验证一下:

$ sudo -u zabbix free -m | awk '$1=="Mem:" {print $3}'
571
$ 

第二步:添加到配置文件中

UserParameter=os.memory.used, free -m | awk '$1=="Mem:" {print $3}'

第三步:测试key
使用 zabbix_agentd 并且用 -t 选项指定key来进行测试:

$ zabbix_agentd -t os.memory.used
os.memory.used                                [t|571]
$ 

测试成功说明写的没问题

第四步:重启agent
要重启agent才能使新的配置文件生效:

$ systemctl restart zabbix-agent
$

之后就可以去Web添加监控项了。

带参数的用法

可以为key设置参数,这样一个设置可以应对多个监控项。
语法如下:

UserParameter=key[*],command

这里的星号表示可以带任意数量的参数,并且似乎也只有这一种用法,没有指定参数数量的写法。

在command中使用参数
在command中使用位置引用$1......$9,来引用key中相应的参数。
另外$0表示命令本身。

关于$符号
由于$1.....$9有了特殊的意义,在awk中的$1也会被zabbix先替换掉。这时应该使用$$1。
zabbix仅仅只替换位置参数,对于单独的$符号或者其他组合(比如$NF),zabbix不会处理。
zabbix仅仅只在使用了key[*],这样指定了key是带参数的时候才会进行位置参数替换的处理。所以之前的示例使用$1没有问题。

修改为带参数的key
现在把之前的示例改成一种更灵活的设置方式:

UserParameter=os.free[*], free -m | awk '$$1~NAME {print $$(COLUMN+1)}' IGNORECASE=1 NAME="$1" COLUMN=$2

测试效果如下:

$ zabbix_agentd -t os.free[mem,2]
os.free[mem,2]                                [t|570]
$ 

示例

一些自定义参数的示例:

UserParameter=Nginx.active[*], /usr/bin/curl -s "http://$1:$2/status" | awk '/^Active/ {print $NF}'
UserParameter=Nginx.reading[*], /usr/bin/curl -s "http://$1:$2/status" | grep 'Reading' | cut -d" " -f2
UserParameter=Nginx.writing[*], /usr/bin/curl -s "http://$1:$2/status" | grep 'Writing' | cut -d" " -f4
UserParameter=Nginx.waiting[*], /usr/bin/curl -s "http://$1:$2/status" | grep 'Waiting' | cut -d" " -f6
UserParameter=Nginx.accepted[*], /usr/bin/curl -s "http://$1:$2/status" | awk '/^([ \t]+[0-9]+){3}/ {print $$1}'
UserParameter=Nginx.handled[*], /usr/bin/curl -s "http://$1:$2/status" | awk '/^([ \t]+[0-9]+){3}/ {print $$2}'
UserParameter=Nginx.requests[*], /usr/bin/curl -s "http://$1:$2/status" | awk '/^([ \t]+[0-9]+){3}/ {print $$3}'

UserParameter=os.free[*], free | awk '$$1~NAME {print $$(COLUMN+1)}' IGNORECASE=1 NAME="$1" COLUMN=$2

UserParameter=MySQL.dml[*] -h$1 -u$2 -p$3 -e 'SHOW GLOBAL STATUS' | awk '/^Com_$4\>/ {print $$2}'

正则表达式 词尾锚定
在调试mysql的时候,遇到一些问题。正则表达式匹配不够精确,有多个值:

$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_select/ {print $0}'       
Com_select      67679
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_update/ {print $0}'       
Com_update      1098
Com_update_multi        0
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete/ {print $0}'       
Com_delete      678
Com_delete_multi        0
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_insert/ {print $0}'       
Com_insert      38494
Com_insert_select       0
$ 

这里是加了词尾锚定:

$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete\>/ {print $0}'  
Com_delete      708
$ 

词尾锚定是 \>,顺便词首就是 \<。

其实也没那么复杂,还有很多办法:

$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete[" "\t]/ {print $0}'   
Com_delete      712
$ mysql -e 'SHOW GLOBAL STATUS' | awk '/^Com_delete[^_]/ {print $0}'      
Com_delete      715
$ mysql -e 'SHOW GLOBAL STATUS' | awk '$1 == "Com_delete" {print $0}' 
Com_delete      717
$

system.run

这个是zabbix内置key,也能够实现同样的功能,那么到底用哪个好?

内置key system.run
这是一个内置key:

system.run[command,]

在主机上指定的命令的执行。返回命令执行结果的文本值。如果指定NOWAIT的模式,这将返回执行命令的结果1。

  • command: 命令
  • mode: wait (默认值, 执行超时时间), nowait (不等待)
  • 最大可用返回512KB数据,包含空白数据。命令输出数据必须是文本

默认agent不支持,安全隐患还是很大的。需要agent端开启RemoteCommand,允许远程执行命令。

优劣比较
通过这个也能实现自定义监控功能,而且不用去agent上定义UserParameter。直接在web就能完成全部操作。这个可能是好处。
用UserParameter,如果agent多,需要每一台agent上都去设置UserParameter,这就很烦。不过还有自动化运维工具可以解决批量更新、操作文件的问题。


网页名称:awk命令与Zabbix自定义参数的介绍
文章分享:http://hbruida.cn/article/gjjdsi.html