1. 程式人生 > 實用技巧 >九,awk陣列詳解

九,awk陣列詳解

  注:在閱讀這篇文章之前,最好已經瞭解了一些開發的基本語法,比如,for迴圈、陣列的基本使用 等,否則在閱讀時 有可能遇到障礙。

  前文中提及過,awk其實可以算作一門指令碼語言,因為它包含了一個指令碼語言的各種語法結構,比如條件判斷語句,比如迴圈語句,那麼,awk中能否使用"陣列"呢?必須能啊,今天我們就來聊聊awk中的陣列。

  如果你有過任何一種程式語言的使用經驗,那麼你一定知道,我們可以通過陣列的下標(或者稱索引),引用陣列中的元素,其他語言中,陣列的下標通常由0開始,也就是說,如果想要引用陣列中的第1個元素,則需要引用對應的下標"[0]",awk中的陣列也是通過引用下標的方法,獲取陣列中的元素的,但是在awk中,陣列元素的下標預設從1開始,但是為了相容你的使用習慣,我們也可以從0開始設定下標,此處不用糾結,到後面自然會明白,我們先來看一個最簡單的示例。

  在其他語言中,你可能會習慣性的先"宣告"一個數組,在awk中,則不用這樣,直接為陣列中的元素賦值即可,示例如下。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";print huluwa[1]}'
二娃
[root@node1 ~]# 

  如上圖所示,為了方便示例,上例中使用了BEGIN模式,在BEGIN模式中,存在一個名為"葫蘆娃"(拼音)的陣列,我們在這個陣列中放置了3個元素,第1個元素為"大娃",第2個元素為"二娃",第3個元素為"三娃",如果我們想要引用陣列中第二個元素的值,只要引用下標為1的元素即可,正如上圖所示,我們使用下標"[1]",獲得了huluwa這個陣列中第二個元素的值,即"二娃"。

  當然,如果你想要看到更多的"葫蘆娃",可以在數組裡面放置更多的元素。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[6]="七娃";print huluwa[1]}'
二娃
[root@node1 ~]# 

  如上圖所示,由於命令太長,可讀性可能會降低,為了在編寫時提高命令的可讀性,我們可以使用Linux命令列的"換行符"進行換行,Linux中,命令列的換行符為反斜槓"\",上述命令換行後,如下。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";\
> huluwa[3]="四娃";huluwa[4]="五娃";huluwa[6]="七娃";print huluwa[1]}'
二娃

  我們知道,在動畫中,六娃的超能力是"隱身",所以六娃也叫"隱身娃",那麼,我們就把上述陣列中的第5個元素的值設定為"空字串"吧,用空字串表示六娃已經"隱身"了,示例如下。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";print huluwa[5]}'

[root@node1 ~]# 

  如上圖所示,上例陣列中的第5個元素的值被設定為了"空字串",當我們列印陣列中的第5個元素的值時,打印出的值就是"空"(注:"空格"不為"空")。

  為什麼要舉這個例子呢?之所以舉這個例子,是因為在awk中,元素的值可以設定為"空",在awk中,將元素的值設定為"空字串"是合法的。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";if(huluwa[5]==""){print "陣列的第六個元素不存在"}}'
陣列的第六個元素不存在
[root@node1 ~]# 

 正如上圖所示,第6個元素明明已經存在,但是通過上述方法判斷元素是否存在時,仍然顯示對應的元素不存在。

  其實,使用上述方法判斷元素是否存在之所以不合理,除了上述原因,還有另外一個原因,就是當一個元素不存在於陣列時,如果我們直接引用這個不存在的元素,awk會自動建立這個元素,並且預設為這個元素賦值為"空字串",示例如下。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";{print huluwa[6]}}'

[root@node1 ~]# 

  如上圖所示,陣列中並沒有第7個元素,但是當我們輸出第7個元素時,輸出了"空",所以,出於此原因,在awk中使用之前的方法判斷元素是否為空也是不合理的,因為當我們引用一個不存在於陣列中的元素時,這個元素其實已經被賦值為"空字串"了

  那麼,在awk中,應該怎樣判斷元素是否存在呢?我們可以使用如下語法。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";if(5 in huluwa){print "陣列的第六個元素存在列印這句話"}}'
陣列的第六個元素存在列印這句話
[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";if(6 in huluwa){print "陣列的第七個元素存在列印這句話"}}'
[root@node1 ~]# 

  如上圖所示,我們可以使用語法 "if(下標 in 陣列名)" ,從而判斷陣列中是否存在對應的元素。

  當然,我們還可以使用 "!" 對條件進行取反,如下圖所示。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="";if (! (6 in huluwa)){print "陣列的第七個元素不存在列印這句話"}}'
陣列的第七個元素不存在列印這句話

  在awk中,陣列的下標不僅可以為"數字",還可以為"任意字串",如果你使用過shell中的陣列,你可以把awk的陣列比作bash中的"關聯陣列",示例如下

[root@node1 ~]# awk 'BEGIN{ huluwa["yiwa"]="大娃";huluwa["erwa"]="二娃";huluwa["sanwa"]="三娃";huluwa["siwa"]="四娃";huluwa["wuwa"]="五娃";huluwa["liuwa"]="六娃";print huluwa["wuwa"]}'
五娃

  其實,awk中的陣列本來就是"關聯陣列",之所以先用以數字作為下標的陣列舉例,是為了讓讀者能夠更好的過度,不過,以數字作為陣列下標的陣列在某些場景中有一定的優勢,但是它本質上也是關聯陣列,awk預設會把"數字"下標轉換為"字串",所以,本質上它還是一個使用字串作為下標的關聯陣列。

  使用delete可以刪除陣列中的元素,如下所示

[root@node1 ~]# awk 'BEGIN{ huluwa["yiwa"]="大娃";huluwa["erwa"]="二娃";huluwa["sanwa"]="三娃";print huluwa["yiwa"];delete huluwa["yiwa"];print huluwa["yiwa"]}'
大娃

[root@node1 ~]# 

  到目前為止,我們已經介紹了怎樣為陣列中的元素賦值、怎樣輸出陣列中的某個元素、以及怎樣刪除陣列中的元素,那麼現在,我們來聊聊在awk中怎樣輸出陣列中的所有元素,在awk中,如果想要輸出陣列中的所有元素,則需要藉助for迴圈語句,還記得在前文中介紹for迴圈時,有兩種for迴圈語法嗎?我們來回顧一下。

#for迴圈語法格式1
for(初始化;布林表示式;更新){
//程式碼語句
}

#for迴圈語法格式2
for(變數in陣列){
//程式碼語句
}

  這兩種for迴圈語法都能夠遍歷輸出陣列中的元素,不過第一種for迴圈語法只能輸出以數字作為下標的陣列,示例如下

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="六娃";huluwa[6]="七娃";for(i=0;i<=6;i++) print huluwa[i]}'
大娃
二娃
三娃
四娃
五娃
六娃
七娃

  你一定看出來了,我們利用了for迴圈中的變數"i"與陣列中的下標都是"數字"的這一特性,按照順序輸出了陣列中的元素值。

  那麼,當陣列中的元素的下標為"無規律的字串"時,我們該怎麼辦呢?這時可以使用for迴圈的第二種語法,示例如下。

[root@node1 ~]# awk 'BEGIN{ huluwa["yiwa"]="大娃";huluwa["erwa"]="二娃";huluwa["sanwa"]="三娃";huluwa["siwa"]="四娃";huluwa["wuwa"]="五娃";huluwa["liuwa"]="六娃";for(i in huluwa )print i,huluwa[i]}'
liuwa 六娃
siwa 四娃
wuwa 五娃
yiwa 大娃
erwa 二娃
sanwa 三娃
[root@node1 ~]# 

  注意,在這種語法中,for迴圈中的變數"i"表示的是元素的下標,而並非表示元素的值,所以,如果想要輸出元素的值,則需要使用"print 陣列名[變數]"

  細心如你,一定發現了一個小問題,當陣列中的下標為"字串"時,元素值輸出的順序與元素在陣列中的順序不同,這是因為awk中的陣列本質上是關聯陣列,所以預設打印出的元素是無序的。

  那麼你可能會提問了,既然之前說過,數字下標最終也會被轉換成 "字串",本質上也是關聯陣列,既然都屬於關聯陣列,那麼為什麼第一種for迴圈語法能夠按照順序輸出陣列中的元素值呢?

  這就是以數字作為下標的優勢,因為第一種for迴圈語法中的變數"i"為數字,由於for迴圈的原因,"i"是按照順序遞增的,當"i"的值與下標的值相同時,我們即可按照下標的順序,輸出對應元素的值,換句話說就是,我們是通過下標的順序,輸出對應元素值的順序,也就是鍵值定位。但是,即使陣列元素的下標為數字,如果使用第二種for迴圈語法,也不能夠按照順序輸出,示例如下。

[root@node1 ~]# awk 'BEGIN{ huluwa[0]="大娃";huluwa[1]="二娃";huluwa[2]="三娃";huluwa[3]="四娃";huluwa[4]="五娃";huluwa[5]="六娃";huluwa[6]="七娃";for(i in huluwa) print i,huluwa[i]}'
4 五娃
5 六娃
6 七娃
0 大娃
1 二娃
2 三娃
3 四娃

  上例又印證我們之前所說的,awk中的陣列本質上就是關聯陣列。

  我想,經過上述對比,你應該已經明白了。

  前文中,我們都是手動的為陣列中的元素賦值,那麼我們能不能將指定的文字分割,然後將分割後的欄位自動賦值到陣列的元素中呢?答案是必須的,但是如果我們想要實現這樣的效果,需要藉助於split函式,而我們還沒有介紹過函式,所以此處就先跳過了,不過需要提前說明的是,通過split函式生成的陣列的下標預設是從1開始的,這就是為什麼之前說,awk中陣列的下標預設是從1開始的了。

  例項應用

  在實際的工作中,我們往往會使用陣列,統計某些字元出現的次數,比如,我們想要統計日誌中每個IP地址出現了多少次,我們就可以利用陣列去統計。

  但是,統計的時候需要配合一些特殊用法,彆著急,我們慢慢聊。

  在awk中,我們可以進行數值運算,示例如下

[root@node1 ~]# awk 'BEGIN{a=1 ; print a ; a=a+1 ; print a}'
1
2
[root@node1 ~]# awk 'BEGIN{a=1 ; print a ; a++ ; print a}'
1
2

  我們將變數a的值設定為1,進行加法計算,每次自加後,再次列印變數a的值,都會加1

  這並不難理解,因為上例中,a的值本來就是一個數字。

  那麼,如果變數a的值是一個字串,我們能否對變數a進行自加運算呢?我們來試試。

[root@node1 ~]# awk 'BEGIN{a="test" ; print a ; a++ ; print a ; a=a+1 ; print a}'
test
1
2

  如上圖所示,在awk中,當變數a的值為字串時,竟然也可以進行加法運算,從上例可以看出,awk中,如果字串參與運算,字串將被當做數字0進行運算。

  那麼"空字串"呢?當空字串參與運算時,也會被當做數字0嗎?我們來試試。

[root@node1 ~]# awk 'BEGIN{a="" ; print a ; a++ ; print a ; a=a+1 ; print a}'

1
2
[root@node1 ~]# 

  看樣子,我們猜的不錯,空字串在參與運算時,也會被當做數字0

  之前說過,當我們直接引用一個數組中不存在的元素時,awk會自動建立這個元素,並且為其賦值為"空字串"。

  所以,如果我們引用一個不存在元素,並對其進行自加運算,那麼會出現什麼效果呢?我們來試一試

[root@node1 ~]# awk 'BEGIN{print testarr["ip"];testarr["ip"]++;print testarr["ip"]}'

1

  如上圖所示,當引用了一個不存在的元素時,元素被賦值為空字串,當對這個元素進行自加運算時,元素的值就變成了1,因為,空字串在參與運算時,被當做0使用了,所以,綜上所述,我們對一個不存在的元素進行自加運算後,這個元素的值就變成了自加運算的次數,自加x次,元素的值就被賦值為x,自加y次,元素的值就被賦值為y,示例如下。

[root@node1 ~]# awk 'BEGIN{print testarr["ip"];testarr["ip"]++;print testarr["ip"];testarr["ip"]++;print testarr["ip"]}'

1
2

  利用這一點,我們就可以統計文字中某些字元出現的次數,比如IP地址,示例如下。

[root@node1 ~]# 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@node1 ~]# 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

  當然,看懂上圖中的命令,需要掌握前文中的知識,同時需要理解今天所介紹的知識。

  上圖中,我們使用了一個空模式,一個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@node1 ~]# awk '{count[$1]++} END{for(i in count) print i,count[i]}' test10|sort -n -r -k2
192.168.1.3 4
192.168.1.2 3
192.168.1.1 2
192.168.1.12 1

  sort引數解析

sort 
-n #按數值排序,預設按字串排序 
-r  #反序
-k2 #使用第二列排序 預設使用第一列排序

  比如,如果我們想要統計如下文字中每個人名出現的次數,我們則可以使用如下命令。

[root@node1 ~]# 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
ALlen 1
Lucas 1
William 1
Thomas 1
Green 1
Jack 1
Phillips 1
Kevin 2
Lee 2
Aiden 1

  關於awk中陣列的用法,就先總結到這裡,這些知識已經能夠滿足我的日常使用了,但是這些並不是陣列的全部,如果你想要更加深入的瞭解陣列,可以參考官方  手冊的陣列部分,連結如下。

  http://www.gnu.org/software/gawk/manual/gawk.html#Arrays