您好,登錄后才能下訂單哦!
awk是一種處理文本文件的語言,是一個強大的文本分析公具。
awk處理文本和數據的方式:逐行讀入文本,尋找匹配特定模式的行,然后進行操作。
功能很強大,所以有很多用處。這里我主要關注下面這樣的場景:
逐行讀入文本,按規則匹配特定的行,以空格為默認分隔符將每行切片,輸出其中特定的某個切片(切開的部分可以進行各種分析處理,這里就是要輸出其中以段):
$ 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內置變量:
上面這些變量,有些是直接來使用的。比如$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選項是等價的。
除了用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
通常,對于每個輸入行,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塊可以實現計算統計輸出的功能,暫時用不上,略過。
除了內置變量,也可以定義自定義變量并使用。這部分內容對于靈活的配置非常有用,而且如果自己寫,也會遇到一些坑。
這里變量的賦值在發生在BEGIN塊執行之后的
直接寫在后面:
$ echo | awk '{print key1,key2}' key1=v1 key2=V2
v1 V2
$
這種用法在BEGIN塊中是識別不了變量的。BEGIN塊的執行在這些變量定義之前。不過還有其他的方法可以用。
另外,這里使用管道作為標準輸入。如果是從文件輸入的話,文件路徑在寫最后。
這里變量額賦值是在BEGIN塊執行的時候
在BEGIN塊中可以對內置變量賦值,同樣的也可以為自定義變量賦值
$ echo | awk 'BEGIN{key1="v1";key2="value2";OFS="_"}{print key1,key2}'
v1_value2
$
這里變量的賦值是在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
$
這里的顯示不明顯,兩邊都是一對雙引號套一個單引號,看圖
解釋說明
先用一個簡單點的環境變量來舉例:
$ 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塊中要這么做,就參考上以小節的做法。
從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 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>,<command>
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
$
這個是zabbix內置key,也能夠實現同樣的功能,那么到底用哪個好?
內置key system.run
這是一個內置key:
system.run[command,<mode>]
在主機上指定的命令的執行。返回命令執行結果的文本值。如果指定NOWAIT的模式,這將返回執行命令的結果1。
默認agent不支持,安全隱患還是很大的。需要agent端開啟RemoteCommand,允許遠程執行命令。
優劣比較
通過這個也能實現自定義監控功能,而且不用去agent上定義UserParameter。直接在web就能完成全部操作。這個可能是好處。
用UserParameter,如果agent多,需要每一臺agent上都去設置UserParameter,這就很煩。不過還有自動化運維工具可以解決批量更新、操作文件的問題。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。