十一,awk”三元運算”與”列印奇偶行”
參考:http://www.zsythink.net/archives/2159
三元運算
還記的我們在學習awk的 "if..else" 結構時,舉的例子嗎?我們來回顧一下。
在centos7中,我們可以判斷使用者的UID是否小於1000,如果使用者的UID大於1000,則使用者為普通使用者,如果使用者的UID小於1000,則使用者為系統使用者。
所以,我們可以通過awk的 "if...else結構",判斷使用者的UID範圍,從而判斷出使用者屬於哪種使用者型別,示例如下
[root@node1 ~]# awk -F'[:]' '{if($3<1000){usertype="系統使用者"}else{usertype="普通使用者"};{print $1,usertype}}' /etc/passwd root 系統使用者 bin 系統使用者 daemon 系統使用者 adm 系統使用者 lp 系統使用者 sync 系統使用者 shutdown 系統使用者 halt 系統使用者 mail 系統使用者 operator 系統使用者 games 系統使用者 ftp 系統使用者 nobody 系統使用者 systemd-network 系統使用者 dbus 系統使用者 polkitd 系統使用者 sshd 系統使用者 postfix 系統使用者 chrony 系統使用者 zabbix 系統使用者 rpc 系統使用者 rpcuser 系統使用者 nfsnobody 普通使用者 ntp 系統使用者 libstoragemgmt 系統使用者 ceph 系統使用者 apache 系統使用者 jack 普通使用者 owen 普通使用者 tss 系統使用者 liuym 普通使用者 tcpdump 系統使用者 zsy1 普通使用者 zsy3 普通使用者 zsy2 普通使用者
正如上圖所示,我們使用"if...else"結構,對usertype變數進行了賦值,如果使用者的UID小於1000,則對usertype變數賦值為"系統使用者",否則則賦值usertype變數為"普通使用者",最後打印出使用者名稱所在的列與usertype變數的值。
其實,我們可以使用三元運算,替換上例中的"if...else"結構語句,示例如下
[root@node1 ~]# awk -F'[:]' '{usertype=$3<1000?"系統使用者":"普通使用者";{print $1,usertype}}' /etc/passwd root 系統使用者 bin 系統使用者 daemon 系統使用者 adm 系統使用者 lp 系統使用者 sync 系統使用者 shutdown 系統使用者 halt 系統使用者 mail 系統使用者 operator 系統使用者 games 系統使用者 ftp 系統使用者 nobody 系統使用者 systemd-network 系統使用者 dbus 系統使用者 polkitd 系統使用者 sshd 系統使用者 postfix 系統使用者 chrony 系統使用者 zabbix 系統使用者 rpc 系統使用者 rpcuser 系統使用者 nfsnobody 普通使用者 ntp 系統使用者 libstoragemgmt 系統使用者 ceph 系統使用者 apache 系統使用者 jack 普通使用者 owen 普通使用者 tss 系統使用者 liuym 普通使用者 tcpdump 系統使用者 zsy1 普通使用者 zsy3 普通使用者 zsy2 普通使用者
正如上圖所示,紅線標註部分則使用了三元運算的語法,代替了之前"if...else"的語法,而三元運算的語法如下:
條件 ? 結果1 : 結果2
上述語法表示,如果條件成立,則返回結果1,如果條件不成立,則返回結果2。
而上例中,"$3<1000"就是上述語法中的"條件","系統使用者"就是上述語法中"?"後面的"結果1","普通使用者"就是上述語法中":"後面的"結果2" ,同時,在上例中我們使用usertype變數接收了三元運算後的返回值,所以,當條件成立時,usertype變數被賦值為"系統使用者",當條件不成立時,usertype變數被賦值為"普通使用者"。
是不是很方便?其實,三元運算還有另外一種使用方式,示例如下
[root@node1 ~]# awk -F'[:]' '{$3<1000?a++:b++}END{print a,b}' /etc/passwd 28 7
我們通過上述命令,統計出了,系統使用者有42個,普通使用者有7個,上圖中紅線標註的用法可以理解為三元運算的另一種語法。如下
表示式1 ? 表示式2 : 表示式3
上述語法表示,如果表示式1為真,則執行表示式2,如果表示式1為假,則執行表示式3
而上例中,"$3<1000"即為表示式1,"a++"即為表示式2,"b++"即為表示式3
也就是說,當每遇到一個UID小於500的使用者,我就對變數a加1,否則我就對變數b加1,從而算出了系統使用者與普通使用者的數量,最後再END模式中輸出了變數a與變數b的值。
列印奇偶行
如果我們想要使用awk列印文字中的奇數行或者偶數行,則是非常簡單的。
列印奇數行
使用行號除以2取餘數如果為1則列印整行
[root@node1 ~]# awk '{if(NR%2==1) print $0}' test12 第 1 行 第 3 行 第 5 行 第 7 行 第 9 行 第 11 行
同理列印偶數行
[root@node1 ~]# awk '{if(NR%2==0) print $0}' test12 第 2 行 第 4 行 第 6 行 第 8 行 第 10 行
還有更簡潔的方法列印奇數行和偶數行
[root@node1 ~]# awk '(i=!i)' test12 第 1 行 第 3 行 第 5 行 第 7 行 第 9 行 第 11 行 [root@node1 ~]# awk '!(i=!i)' test12 第 2 行 第 4 行 第 6 行 第 8 行 第 10 行
正如上圖所示,test12檔案中有11行文字,我們可以使用非常簡潔的awk命令,打印出了奇數行或者偶數行。
但是如果我們想要徹底搞明白原理,則需要搞明白如下兩個知識點(後面會有更詳細的解釋)
1、在awk中,如果省略了模式對應的動作,當前行滿足模式時,預設動作為列印整行,即{print $0}。
2、在awk中,0或者空字串表示"假",非0值或者非空字串表示"真"
上述兩個知識點是什麼意思呢?我們慢慢聊。
在之前介紹awk模式的文章中提及過,模式可以理解為條件,如果當前行能與模式匹配,則會執行對應的動作。示例如下
[root@node1 ~]# awk '/1/{print $0}' test12 第 1 行 第 10 行 第 11 行 [root@node1 ~]# awk '$2>10{print $0}' test12 第 11 行 [root@node1 ~]#
上圖中的兩個命令均使用到了模式
第一個命令表示如果當前行中包含字元"1",則執行對應的動作,而對應的動作就是列印整行。
第二個命令表示如果test12文字中文字行的第二列的值如果大於10,則執行對應的動作,而對應的動作就是列印整行。
[root@node1 ~]# awk '/1/' test12 第 1 行 第 10 行 第 11 行 [root@node1 ~]# awk '$2>10' test12 第 11 行 [root@node1 ~]#
我們發現,當使用了模式時,如果省略了對應的動作,會預設的輸出整行。
也就是說,當使用了模式時,如果省略了模式對應的動作,預設動作為"{print $0}"
當然,"空模式"與"BEGIN/END模式"除外。
這就是第1個知識點的含義,我想你應該明白了,那麼我們來聊聊第2個知識點。
在awk中,0或者空字串表示"假",非0值或者非空字串表示"真",什麼意思呢?我們還是可以從模式說起,"模式"可以理解為"條件",當條件成立,則為真,當條件不成立,則為假,所以,當模式為真時,則會執行對應的動作,當模式為假時,則不會執行對應的動作。
那麼,我們能不能直接把模式替換為"真"或者"假"呢?我們來試試。
上例中,命令1使用了"空模式",也就是說,每一行都滿足模式,每一行經過"空模式"匹配以後結果都是"真",所以每一行都會執行對應的動作。
命令2中,原來"模式的位置"被替換為了數字"1",我們可以把數字"1"理解成一種模式匹配後的結果,而1是非零值,剛才說過,在awk中非零值表示真,所以,"1"表示"真", 換句話說就是模式的匹配結果為真,模式成立則會執行對應的動作,而命令2中,對應的動作為列印整行。
命令3 與 命令2 同理,在命令3中, 數字"2"為非零值,表示真,可以理解為:模式的匹配結果為真,則會執行對應的動作,聰明如你一定想到了,數值"2"可以換做任何非0值或者非空字串。
命令4中,數字"2"為非零值,表示模式為真,而之前說過,當使用模式時,可以省略動作,當使用模式並省略動作時,預設動作為列印整行,所以,命令4表示列印所有行,因為每一行的模式都為真。
命令5與命令6同理,在awk中,數字"0"與空字串表示假,當模式為假時,不會執行對應的動作,而當存在模式並省略動作時,預設動作為列印整行,但是由於模式為假,所以對應的動作並未執行。
其實,我們還能對真與假進行取反,非真即為假,非假即為真,示例如下。
[root@node1 ~]# awk '0' test3 [root@node1 ~]# awk '!0' test3 hey heey heeey heeeey [root@node1 ~]#
如果你已經看懂了上面的例子,那麼,我們再來延伸一下。
你猜猜,如下示例會輸出什麼?
[root@node1 ~]# awk 'i=1' test3
沒錯,聰明如你一定想到了,上例中,其實是使用了awk的變數,將變數 i 賦值為1,當 i=1 以後,i為非零值,表示為真,我們可以認為這是一種模式匹配後的結果,當模式為真時,同時省略了對應動作時,預設動作為列印整行,所以上例會輸出test3中的所有行。
[root@node1 ~]# awk 'i=1' test3 hey heey heeey heeeey [root@node1 ~]# awk 'i=5' test3 hey heey heeey heeeey [root@node1 ~]# awk 'i="a"' test3 hey heey heeey heeeey [root@node1 ~]# awk 'i=a' test3 [root@node1 ~]# awk 'i=0' test3 [root@node1 ~]# awk 'i=""' test3
理解完上述示例以後,我們再回過頭來,看看之前列印奇數行的示例,你可能就會明白了。
[root@node1 ~]# awk 'i=!i' test12 第 1 行 第 3 行 第 5 行 第 7 行 第 9 行 第 11 行 [root@node1 ~]#
當awk開始處理第一行時,變數 i 被初始化,變數 i 在被初始化時,值為"空",而awk中,數字0或者"空字串"表示假,所以可以認為模式為假,但是 i 直接取反了,對假取反後的值為真,將取反後的值又賦值給了變數i,此刻,變數i的值為真,所以當awk處理第一行文字時,變數i的值被賦值為真,模式成立則需要執行對應的動作,而上例中又省略了動作,所以預設動作為"{print $0}",所以,第一行被整行列印了。
當第一行文字處理完畢後,awk開始處理第二行文字,此時,i 為真,但是取反後,i 為假,所以第二行沒有被輸出,依次類推,最終只打印了奇數行。
為了能夠更加直觀的看到上述過程,我們將i的值打印出來,通過如下動作,能夠打印出處理每一行時,i 對應的值。
[root@node1 ~]# awk '{i=!i;print i}' test12 1 0 1 0 1 0 1 0 1 0 1
當然,聰明如你,我就不用再解釋列印偶數行的原理了,我想你應該已經能夠舉一反三了。