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

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