在文本處理的工作中,awk的數組是必不可少的工具。
awk的數組,一種關聯數組(Associative Arrays),下標可以是數字和字符串。因無需對數組名和元素提前聲明,也無需指定元素個數 ,所以awk的數組使用非常靈活。
首先介紹下幾個awk數組相關的知識點:
<1>建立數組
- array[index] = value :數組名array,下標index以及相應的值value。
複製代碼
<2>讀取數組值
- { for (item in array) print array[item]} # 輸出的順序是隨機的
- {for(i=1;i<=len;i++) print array[i]} # Len 是數組的長度
複製代碼
<3>多維數組,array[index1,index2,……]:SUBSEP是數組下標分割符,默認為“\034”。可以事先設定SUBSEP,也可以直接在SUBSEP的位置輸入你要用的分隔符,如:
- awk 'BEGIN{SUBSEP=":";array["a","b"]=1;for(i in array) print i}'
- a:b
- awk 'BEGIN{array["a"":""b"]=1;for(i in array) print i}'
- a:b
複製代碼
但,有些特殊情況需要避免,如:
- awk 'BEGIN{
- SUBSEP=":"
- array["a","b:c"]=1 # 下標為“a:b:c”
- array["a:b","c"]=2 #下標同樣是“a:b:c”
- for (i in array) print i,array[i]}'
- a:b:c 2 #所以數組元素只有一個。
複製代碼
<4>刪除數組或數組元素: 使用delete 函數
- delete array #刪除整個數組
- delete array[item] # 刪除某個數組元素(item)
複製代碼
<5> 排序:awk中的asort函數可以實現對數組的值進行排序,不過排序之後的數組下標改為從1到數組的長度。在gawk 3.1.2以後的版本還提供了一個asorti函數,這個函數不是依據關聯數組的值,而是依據關聯數組的下標排序,即asorti(array)以後,仍會用數字(1到數組長度)來作為下標,但是array的數組值變為排序後的原來的下標,除非你指定另一個參數如:asorti(a,b)。(非常感謝lionfun對asorti的指正和補充)
- echo 'aa
- bb
- aa
- bb
- cc' |\
- awk '{a[$0]++}END{l=asorti(a);for(i=1;i<=l;i++)print a[i]}'
- aa
- bb
- cc
- echo 'aa
- bb
- aa
- bb
- cc' |\
- awk '{a[$0]++}END{l=asorti(a,b);for(i=1;i<=l;i++)print b[i],a[b[i]]}'
- aa 2
- bb 2
- cc 1
複製代碼
下面說awk數組的實際應用。
1. 除去重複項, 這個不多說, 只給出代碼:
- awk '!a[$0]++' file(s)
- awk '!($0 in a){a[$0];print}' file(s)
複製代碼
另一種:http://bbs.chinaunix.net/thread-1859344-1-1.html
2. 計算總數(sum),如:
- awk '{name[$0]+=$1};END{for(i in name) print i, name[i]}'
- 再舉個例子:
- echo "aaa 1
- aaa 1
- ccc 1
- aaa 1
- bbb 1
- ccc 1" |awk '{a[$1]+=$2}END{for(i in a) print i,a[i]}'
- aaa 3
- bbb 1
- ccc 2
複製代碼
3. 查看文件差異。
- cat file1
- aaa
- bbb
- ccc
- ddd
- cat file2
- aaa
- eee
- ddd
- fff
複製代碼
<1> 合併file1和file2,除去重複項:
- awk 'NR==FNR{a[$0]=1;print} #讀取file1,建立數組a,下標為$0,並賦值為1,然後打印
- NR>FNR{ #讀取file2
- if(!(a[$0])) {print } #如果file2 的$0不存在於數組a中,即不存在於file1,則打印。
- }' file1 file2
- aaa
- bbb
- ccc
- ddd
- eee
- fff
複製代碼
<2> 提取文件1中有,但文件2中沒有:
- awk 'NR==FNR{a[$0]=1} #讀取file2,建立數組a,下標為$0,並賦值為1
- NR>FNR{ #讀取file1
- if(!(a[$0])) {print } #如果file1 的$0不存在於數組a中,即不存在於file2,則打印。
- }' file2 file1
- bbb
- ccc
複製代碼
另:http://bbs.chinaunix.net/viewthr ... &page=1#pid15547885
4. 排序:
- echo "a
- 1
- 0
- b
- 2
- 10
- 8
- 100" |
- awk '{a[$0]=$0} #建立數組a,下標為$0,賦值也為$0
- END{
- len=asort(a) #利用asort函數對數組a的值排序,同時獲得數組長度len
- for(i=1;i<=len;i++) print i "\t"a[i] #打印
- }'
- 1 0
- 2 1
- 3 2
- 4 8
- 5 10
- 6 100
- 7 a
- 8 b
複製代碼
5. 有序輸出:採用(index in array)的方式打印數組值的順序是隨機的,如果要按原序輸出,則可以使用下面的方法:http://bbs2.chinaunix.net/viewthread.php?tid=1811279
- awk '{a[$1]=$2
- c[j++]=$1}
- END{
- for(m=0;m<j;m++)print c[m],a[c[m]]
- }'
複製代碼
6. 多個文本編輯:這裡主要指的是待處理的文本之間的格式上有區別,如分隔符不同,;或是待處理文本需提取的信息的位置不同,如不同的列或行。
<例1>:
- cat file1
- g1.1 2
- g2.2 4
- g2.1 5
- g4.1 3
- cat file2
- g1.1 2
- g1.2 3
- g4.1 4
- cat file3
- g1.2 3
- g5.1 3
複製代碼
要求輸出:
- g1.1 2 2 -
- g1.2 - 3 3
- g2.2 4 - -
- g2.1 5 - -
- g4.1 3 4 -
- g5.1 - - 3
複製代碼
實現代碼如下:
- awk '{a[ARGIND" "$1]=$2 # ARGIND是當前命令行文件的位置(從0開始),將它和第一列的value作為下標,建立數組a。
- b[$1] #將第一列的value作為下標,建立數組b,目的是在讀完所有文件之後,能得到第一列value的uniqe-list。
- }
- END{
- for(i in b) {
- printf i" "
- for(j=1;j<=ARGIND;j++) printf "%s ", a[j" "i]?a[j" "i]:"-" #此時的ARGIND值為3.
- print ""
- }
- }' file1 file2 file3
複製代碼
這裡是利用awk的內置變量ARGIND來處理完成對文件的處理。關於ARGIND,ARGV,ARGC的使用,大家可以參考:http://bbs.chinaunix.net/viewthr
... 0335&from=favorites。
當然,我們也可以利用另外一個內置變量FILENAME來完成相同的任務(大家可以先想想怎麼寫),如下:
- awk '{a[FILENAME" "$1]=$2;b[$1];c[FILENAME]}END{for(i in b) {printf i" ";for(j in c) printf "%s ", a[j" "i]?a[j" "i]:"-";print""}}' file1 file2 file3
複製代碼
<例2>:對上面的數據的格式稍作改動,每個文件的分隔符都一樣的情況,但輸出要求不變:
- cat file1
- g1.1|2
- g2.2|4
- g2.1|5
- g4.1|3
- cat file2
- g1.1#2
- g1.2#3
- g4.1#4
- cat file3
- g1.2@3
- g5.1@3
複製代碼
實現代碼如下:
- awk '{a[ARGIND" "$1]=$2
- b[$1]
- }
- END{
- for(i in b) {
- printf i" "
- for(j=2;j<=ARGIND;j+=2) printf "%s ", a[j" "i]?a[j" "i]:"-" # 由於FS的設置也是有對應ARGIND值,所以對ARGIND稍作改動。
- print ""
- }
- }' FS="|" file1 FS="#" file2 FS="@" file3 # 對每個文件分別設置FS的值。
複製代碼
因為這個例子的數據比較簡單,我們也可以在BEGIN模塊中完成對FS值設置,如下:
- awk 'BEGIN{FS="[|#@]"}{a[ARGIND" "$1]=$2; b[$1]}END{for(i in b) {printf i" ";for(j=1;j<=ARGIND;j++) printf "%s ", a[j" "i]?a[j" "i]:"-"; print ""}}' file1 file2 file3
複製代碼
利用FILENAME 同樣可以解決問題:
- awk '
- FILENAME=="file1"{FS="|"} # 設置FS
- FILENAME=="file2"{FS="#"} #設置FS
- FILENAME=="file3"{FS="@"} #設置FS
- # 稍顯繁瑣,不過一目了然
- {$0=$0} #使FS生效。
- {a[ARGIND" "$1]=$2; b[$1]}
- END{ for(i in b) {printf i" "; for(j=1;j<=ARGIND;j++) printf "%s ", a[j" "i]?a[j" "i]:"-"; print ""}
- }' file1 file2 file3
複製代碼
推薦一個關於數組處理文件的帖子http://www.chinaunix.net/jh/24/577044.html ,裡面有不少例子供大家學習。
7. 文本翻轉或移位:二維或多維數組的應用
<例1>:
- Inputfile
- 1 2 3 4 5 6
- 2 3 4 5 6 1
- 3 4 5 6 1 2
- 4 5 6 1 2 3
- Outputfile
- 4 3 2 1
- 5 4 3 2
- 6 5 4 3
- 1 6 5 4
- 2 1 6 5
- 3 2 1 6
- awk '{
- if (max_nf < NF)
- max_nf = NF # 數組第一維的長度
- max_nr = NR # 數組第二維的長度
- for (x = 1; x <= NF; x++)
- vector[x, NR] = $x #建立數組vector
- }
- END {
- for (x = 1; x <= max_nf; x++) {
- for (y = max_nr; y >= 1; --y)
- printf("%s ", vector[x, y])
- printf("\n")
- }
- }'
複製代碼
<例2>:來自http://bbs.chinaunix.net/viewthr ... &page=1#pid13339226
有兩個文本a和b,要求輸出c文本,合併的規則是按照第一行的headline(按字母順序)合併文本a和b,空缺按“0”補齊。
- cat a.txt
- a b c d
- 1 2 9 7
- 4 5 8 9
- 5 3 6 1
- cat b.txt
- a e f d g
- 9 2 4 7 3
- 4 3 7 9 4
- cat c.txt
- a b c d e f g
- 1 2 9 7 0 0 0
- 4 5 8 9 0 0 0
- 5 3 6 1 0 0 0
- 9 0 0 7 2 4 3
- 4 0 0 9 3 7 4
複製代碼
下面我們來參看並解讀下Tim大師的代碼:
- awk '
- FNR==1{ #FNR==1,即a和b文本的第一行,這個用的真的很巧妙。
- for(i=1;i<=NF;i++){
- b[i]=$i #讀取文本的每個元素存入數組b
- c[$i]++} #另建立數組c,並統計每個元素的個數
- next #可以理解為,讀取FNR!=1的文本內容。
- }
- {k++ # 統計除去第一行的文本行數
- for(i=1;i<=NF;i++)a[k","b[i]]=$i #利用一個二維數組來保持每個數字的位置, k,b[i]可以理解為每個數字的坐標。
- }
- END{
- l=asorti(c) #利用asorti函數對數組的下標進行排序,並獲取數組長度,即輸出文件的列數(NF值)
- for(i=1;i<=l;i++)printf c[i]" " # 先打印第一行,相當於headline。
- print ""
- for(i=1;i<=k;i++){
- for(j=1;j<=l;j++)printf a[i","c[j]]?a[i","c[j]]" ":"0 " # 打印二維數組的值。
- print ""}
- }' a.txt b.txt
複製代碼
8. 選擇性打印:
打印某個關鍵字前幾行,以3行為例:
- seq 20 |awk '/\<10\>/{for(i=NR-3;i<NR;i++)print a[i%3];exit}{a[NR%3]=$0}'
- 7
- 8
- 9
複製代碼
利用NR取餘數,建立數組,這是一種非常高效的代碼。
9. 通過split函數建立數組:數組的下標為從1開始的數字。
- split(s, a [, r]) # s:string, a:array name,[,r]:regular expression。
- echo 'abcd' |awk '{len=split($0,a,"");for(i=1;i<=len;i++) print "a["i"] = " a[i];print "length = " len}'
- a[1] = a
- a[2] = b
- a[3] = c
- a[4] = d
- length = 4
複製代碼
10. awk數組使用的小技巧和需要避免的用法:
<1> 嵌套數組:
- awk 'BEGIN{a[1]=3;b[1]=1;print a[b[1]]}'
- 3
複製代碼
<2> 下標設為變量或函數:
- awk 'BEGIN{s=123;a[substr(s,2)]=substr(s,1,1);for(i in a)print "index : "i"\nvalue : "a[i]}'
- index : 23
- value : 1
複製代碼
<3> 不可以將數組名作為變量使用,否則會報錯:
- awk 'BEGIN{a["1"] = 3; delete a;a=3;print a}' #即使你已經使用了delete函數。
- awk: fatal: attempt to use array `a' in a scalar context
複製代碼
<4> 數組的長度:
- length(array)
複製代碼
<5> match 函數也可以建立數組(你知道么?版本要求高於gawk 3.1.2)
- echo "foooobazbarrrrr |
- gawk '{ match($0, /(fo+).+(bar*)/, arr) #匹配到的部分自動賦值到arr中,下標從1開始
- print arr[1], arr[2]
- print arr[1, "start"], arr[1, "length"] #二維數組arr[index,"start"]值=RSTART
- print arr[2, "start"], arr[2, "length"] #二維數組arr[index,"length"]值=RLENGTH
- }'
- foooo barrrrr
- 1 5
- 9 7
複製代碼
<6>想到過用split清空數組么?
- awk 'BEGIN{
- split("abc",array,"")
- print "array[1] = "array[1],"\narray[2] = "array[2],"\narray[3] = "array[3]
- split("",array)
- print "array[1] = "array[1],"\narray[2] ="array[2],"\narray[3] ="array[3]
- }'
- array[1] = a
- array[2] = b
- array[3] = c
- array[1] =
- array[2] =
- array[3] =
複製代碼