awk陣列詳解
在awk中,直接為陣列中的元素賦值即可,示例如下。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";print huluwa[1]}' 二娃
為了方便示例,上例中使用了BEGIN模式,在BEGIN模式中,存在一個名為"葫蘆娃"(拼音)的陣列,我們在這個陣列中放置了3個元素,第1個元素為"大娃",第2個元素為"二娃",第3個元素為"三娃",如果我們想要引用陣列中第二個元素的值,只要引用下標為1的元素即可,正如上圖所示,我們使用下標"[1]",獲得了huluwa這個陣列中第二個元素的值,即"二娃"。
當然,如果你想要看到更多的"葫蘆娃",可以在數組裡面放置更多的元素。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";print huluwa[4]}' 五娃
由於命令太長,可讀性可能會降低,為了在編寫時提高命令的可讀性,我們可以使用Linux命令列的"換行符"進行換行,Linux中,命令列的換行符為反斜槓"\",上述命令換行後,如下
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";\> huluwa[3]="四娃";huluwa[4]="五娃";print huluwa[4]}' 五娃
可以看到,目前葫蘆娃陣列中已經存在6個葫蘆娃了,我們可以獲取到我們想要的葫蘆娃,換句話說,我們可以通過陣列的下標,獲取到任何一個元素的值。
我們知道,在動畫中,五娃的超能力是"隱身",所以六娃也叫"隱身娃",那麼,我們就把上述陣列中的第5個元素的值設定為"空字串"吧,用空字串表示五娃已經"隱身"了,示例如下。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";\ huluwa[3]="四娃";huluwa[4]=" ";print huluwa[4]}' [root@server01 ~]#
既然在awk中,元素的值可以為"空",那麼我們就不能再根據元素的值是否為"空"去判斷元素是否存在了,所以,在awk中,如果你使用如下方法判斷陣列中的元素是否存在,是不合理的,如下圖所示。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";\ huluwa[3]="四娃";huluwa[4]="五娃";print "陣列5是不存在的"}' 陣列6是不存在的
正如上圖所示,第5個元素明明已經存在,但是通過上述方法判斷元素是否存在時,仍然顯示對應的元素不存在。
其實,使用上述方法判斷元素是否存在之所以不合理,除了上述原因,還有另外一個原因,就是當一個元素不存在於陣列時,如果我們直接引用這個不存在的元素,awk會自動建立這個元素,並且預設為這個元素賦值為"空字串",示例如下。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";\ huluwa[3]="四娃";huluwa[4]=" ";print huluwa[5]}' [root@server01 ~]#
如上圖所示,陣列中並沒有第6個元素,但是當我們輸出第7個元素時,輸出了"空",所以,出於此原因,在awk中使用之前的方法判斷元素是否為空也是不合理的,因為當我們引用一個不存在於陣列中的元素時,這個元素其實已經被賦值為"空字串"了,
那麼,在awk中,應該怎樣判斷元素是否存在呢?我們可以使用如下語法。
[root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]=" ";if(4 in huluwa){print "資料的第5個元素存在即可看到這句話"}}' 資料的第5個元素存在即可看到這句話 [root@server01 ~]# awk 'BEGIN{huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]=" ";if(9 in huluwa){print "資料的第5個元素存在即可看到這句話"}}'
我們可以使用語法 "if(下標 in 陣列名)" ,從而判斷陣列中是否存在對應的元素。
在awk中,陣列的下標不僅可以為"數字",還可以為"任意字串",如果你使用過shell中的陣列,你可以把awk的陣列比作bash中的"關聯陣列",示例如下
[root@server01 ~]# awk 'BEGIN{huluwa["dawa"]="大娃";huluwa["erwa"]="二娃";huluwa["shanwa"]="三娃";huluwa["shiwa"]="四娃";huluwa["wuwa"]="五娃";print huluwa["erwa"]}' 二娃
其實,awk中的陣列本來就是"關聯陣列",之所以先用以數字作為下標的陣列舉例,是為了讓讀者能夠更好的過度,不過,以數字作為陣列下標的陣列在某些場景中有一定的優勢,但是它本質上也是關聯陣列,awk預設會把"數字"下標轉換為"字串",所以,本質上它還是一個使用字串作為下標的關聯陣列
使用delete可以刪除陣列中的元素,如下所示
[root@server01 ~]# awk 'BEGIN{huluwa["dawa"]="大娃";huluwa["erwa"]="二娃";huluwa["shanwa"]="三娃";huluwa["shiwa"]="四娃";huluwa["wuwa"]="五娃";delete huluwa["erwa"];print huluwa["erwa"]}' [root@server01 ~]#
也可以使用delete刪除整個陣列,如下所示
[root@server01 ~]# awk 'BEGIN{huluwa["dawa"]="大娃";huluwa["erwa"]="二娃";huluwa["shanwa"]="三娃";huluwa["shiwa"]="四娃";huluwa["wuwa"]="五娃";print huluwa["dawa"];print huluwa["erwa"];\ delete huluwa;print huluwa["dawa"]; print huluwa["erwa"]}' 大娃 二娃 [root@server01 ~]#
到目前為止,我們已經介紹了怎樣為陣列中的元素賦值、怎樣輸出陣列中的某個元素、以及怎樣刪除陣列中的元素,那麼現在,我們來聊聊在awk中怎樣輸出陣列中的所有元素,在awk中,如果想要輸出陣列中的所有元素,則需要藉助for迴圈語句,還記得在前文中介紹for迴圈時,有兩種for迴圈語法嗎?我們來回顧一下。
#for迴圈語法格式1 for(初始化;布林表示式;更新){ //程式碼語句 } #for迴圈語法格式2 for(變數in陣列){ //程式碼語句 }
這兩種for迴圈語法都能夠遍歷輸出陣列中的元素,不過第一種for迴圈語法只能輸出以數字作為下標的陣列,示例如下
[root@server01 ~]# awk 'BEGIN{huluwa[1]="大娃";huluwa[2]="二娃";huluwa[3]="三娃";huluwa[4]="四娃";huluwa[5]="五娃";huluwa[6]="六娃";huluwa[7wq]="七娃";for(i=1;i<=7;i++){print i,huluwa[i]}}' 1 大娃 2 二娃 3 三娃 4 四娃 5 五娃 6 六娃 7 七娃
你一定看出來了,我們利用了for迴圈中的變數"i"與陣列中的下標都是"數字"的這一特性,按照順序輸出了陣列中的元素值。
那麼,當陣列中的元素的下標為"無規律的字串"時,我們該怎麼辦呢?這時可以使用for迴圈的第二種語法,示例如下。
[root@server01 ~]# awk 'BEGIN{huluwa["dawa"]="大娃";huluwa["erwa"]="二娃";huluwa["shanwa"]="三娃";huluwa["shiwa"]="四娃";huluwa["wuwa"]="五娃";huluwa["liuwa"]="六娃";huluwa["qi"]="七娃";for(i in huluwa){print i,huluwa[i]}}' liuwa 六娃 shanwa 三娃 shiwa 四娃 wuwa 五娃 erwa 二娃 qi 七娃 dawa 大娃
注意,在這種語法中,for迴圈中的變數"i"表示的是元素的下標,而並非表示元素的值,所以,如果想要輸出元素的值,則需要使用"print 陣列名[變數]"
當陣列中的下標為"字串"時,元素值輸出的順序與元素在陣列中的順序不同,這是因為awk中的陣列本質上是關聯陣列,所以預設打印出的元素是無序的。
那麼你可能會提問了,既然之前說過,數字下標最終也會被轉換成 "字串",本質上也是關聯陣列,既然都屬於關聯陣列,那麼為什麼第一種for迴圈語法能夠按照順序輸出陣列中的元素值呢?
這就是以數字作為下標的優勢,因為第一種for迴圈語法中的變數"i"為數字,由於for迴圈的原因,"i"是按照順序遞增的,當"i"的值與下標的值相同時,我們即可按照下標的順序,輸出對應元素的值,換句話說就是,我們是通過下標的順序,輸出對應元素值的順序,也就是鍵值定位。但是,即使陣列元素的下標為數字,如果使用第二種for迴圈語法,也不能夠按照順序輸出,示例如下。
[root@server01 ~]# awk 'BEGIN{huluwa[1]="大娃";huluwa[2]="二娃";huluwa[3]="三娃";huluwa[4]="四娃";huluwa[5]="五娃";huluwa[6]="六娃";huluwa[7wq]="七娃";for(i=1;i<=7;i++){print i,huluwa[i]}}' 1 大娃 2 二娃 3 三娃 4 四娃 5 五娃 6 六娃 7 七娃
awk中的陣列本質上就是關聯陣列。
前文中,我們都是手動的為陣列中的元素賦值,那麼我們能不能將指定的文字分割,然後將分割後的欄位自動賦值到陣列的元素中呢?答案是必須的,但是如果我們想要實現這樣的效果,需要藉助於split函式,而我們還沒有介紹過函式,所以此處就先跳過了,不過需要提前說明的是,通過split函式生成的陣列的下標預設是從1開始的,這就是為什麼之前說,awk中陣列的下標預設是從1開始的了。
例項應用
在實際的工作中,我們往往會使用陣列,統計某些字元出現的次數,比如,我們想要統計日誌中每個IP地址出現了多少次,我們就可以利用陣列去統計。
但是,統計的時候需要配合一些特殊用法,彆著急,我們慢慢聊。
在awk中,我們可以進行數值運算,示例如下
[root@server01 ~]# awk 'BEGIN{a=1;print a;a=a+1;print a}' 1 2 [root
[root@server01 ~]# awk 'BEGIN{a=1;print a;a++;print a}' 1 2
我們將變數a的值設定為1,進行加法計算,每次自加後,再次列印變數a的值,都會加1
這並不難理解,因為上例中,a的值本來就是一個數字。
那麼,如果變數a的值是一個字串,我們能否對變數a進行自加運算呢?我們來試試。
[root@server01 ~]# awk 'BEGIN{a="test";print a;a=a+1;print a;a++;print a}' test 1 2
在awk中,當變數a的值為字串時,竟然也可以進行加法運算,從上例可以看出,awk中,如果字串參與運算,字串將被當做數字0進行運算。
那麼"空字串"呢?當空字串參與運算時,也會被當做數字0嗎?我們來試試。
[root@server01 ~]# awk 'BEGIN{a="";print a;a=a+1;print a;a++;print a}' 1 2 [root@server01 ~]# awk 'BEGIN{a=" ";print a;a=a+1;print a;a++;print a}' 1 2
看樣子,我們猜的不錯,空字串在參與運算時,也會被當做數字0
之前說過,當我們直接引用一個數組中不存在的元素時,awk會自動建立這個元素,並且為其賦值為"空字串"。
所以,如果我們引用一個不存在元素,並對其進行自加運算,那麼會出現什麼效果呢?我們來試一試
[root@server01 ~]# awk 'BEGIN{print testarr["ip"];testarr["ip"]++;print testarr["ip"]}' 1 [root@server01 ~]#
當引用了一個不存在的元素時,元素被賦值為空字串,當對這個元素進行自加運算時,元素的值就變成了1,因為,空字串在參與運算時,被當做0使用了,所以,綜上所述,我們對一個不存在的元素進行自加運算後,這個元素的值就變成了自加運算的次數,自加x次,元素的值就被賦值為x,自加y次,元素的值就被賦值為y,示例如下。
[root@server01 ~]# awk 'BEGIN{print testarr["ip"];testarr["ip"]++;testarr["ip"]++;print testarr["ip"]}' 2 [root@server01 ~]#
利用這一點,我們就可以統計文字中某些字元出現的次數,比如IP地址,示例如下。
[root@server01 ~]# cat test10 192.168.1.1 192.168.1.2 192.168.1.3 192.168.1.12 192.168.1.3 192.168.1.3 192.168.1.2 192.168.1.1 192.168.1.2 192.168.1.3
[root@server01 ~]# awk '{count[$1]++}END{for(i in count){print i,count[i]}}' test10 192.168.1.12 1 192.168.1.1 2 192.168.1.2 3 192.168.1.3 4 [root@server01 ~]#
我們使用了一個空模式,一個END模式。
空模式中,我們隨便建立了一個數組,並且將IP地址作為引用元素的下標,進行了引用,所以,當執行到第一行時,我們引用的是count["192.168.1.1"]
很明顯,這個元素並不存在,所以,當第一行被空模式中的動作處理完畢後,count["192.168.1.1"]的值已經被賦值為1了。
由於END模式中的動作會最後執行,所以我們先不考慮END模式。
這時,空模式中的動作繼續處理下一行,而下一行的IP地址為192.168.1.2
所以,count["192.168.1.2"]第一次參與運算的過程與上述過程同理。
其他IP地址第一次參與運算的過程與上述過程同理。
直到再次遇到相同的IP地址時,使用同樣一個IP地址作為下標的元素將會再次被自加,每次遇到相同的IP地址,對應元素的值都會加1。
直到處理完所有行,開始執行END模式中的動作。
而END模式中,我們打印出了count陣列中的所有元素的下標,以及元素對應的值。
此刻,count陣列中的下標即為IP地址,元素的值即為對應IP地址出現的次數。
最終,我們統計出了每個IP地址出現的次數。
其實,我們就是利用了之前所演示的一個知識點:
我們對一個不存在的元素進行自加運算後,這個元素的值就變成了自加運算的次數
上述過程可能比較繞,如果你之前沒有接觸過awk,一遍看不懂是很正常的,自己按照上述過程動手做幾遍,細細品味一番,相信你會搞明白的。
如果你以後再想統計文字中某類文字出現的"次數",就可以使用上述套路了,活學活用以後,你會發現上述套路特別好使。
比如,如果我們想要統計如下文字中每個人名出現的次數,我們則可以使用如下命令。
[root@server01 ~]# awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(j in count){print j,count[j]}}' test4 Tyler 1 Angel 1 James 1 Lucas 1 William 1 Thomas 1 Green 1 Jack 1 Phillips 1 Kevin 2 Lee 2 Allen 1 Aiden 1