Linux awk命令:
AWK是一種處理文本文件的語言,是一個強大的文本分析工具。我們可以利用awk命令,把一個文本整理成我們想要的樣子。例如可以整理成表的樣子。awk其實是一門編程語言,支持判斷、數組、循環等功能。
語法:
awk [選項參數] 'script' var=value file(s) awk [選項參數] -f scriptfile var=value file(s) awk [options] 'pattern{action}' file
參數解釋:
用法一:
awk [options] 'pattern{action}' file
action指的是動作,awk最常用的動作就是print 和 printf 。
1、不指定pattern,使用最簡單的action。執行一個打印動作
[root@dm shell]# echo hello world! > test.log [root@dm shell]# awk '{print}' test.log hello world!
輸出df信息的第5列,使用df | awk '{print $5}' 。$5表示將當前行按照分隔符分割后的第五列。默認空格作為分隔符,awk自動將連續的空格作為一個分隔符。
[root@dm shell]# df 文件系統 1K-塊 已用 可用 已用% 掛載點 /dev/sda3 28696576 9926076 17312788 37% / tmpfs 436768 76 436692 1% /dev/shm /dev/sda1 198337 32627 155470 18% /boot [root@dm shell]# df | awk '{print $5}' 已用% 37% 1% 18% [root@dm shell]# df | awk '{print $4,$5}' 可用 已用% 17312780 37% 436692 1% 155470 18%
awk是逐行處理的,awk處理文本是一行一行處理的,默認以換行符為標記識別每一行。新的一行開始,awk以用戶指定的分隔符處理新的一行。沒有指定默認空格作為分隔符。每個字段按照順序,分別對應到awk的內置變量中。分割完后的第一個字段為$1,第二個字段為$2..... ,類推。$0表示當前處理的整個一行。$NF表示當前行分割后的最后一列。
注意:$NF和NF不一樣,$NF表示分割后的最后一列,NF表示當前行被分割后一共有幾列。
示例:
[root@dm shell]# cat test.log abc 123 ide wdw dwf 23d dw3 45f w2e 1、一次輸出多列,中間用逗號分隔 [root@dm shell]# awk '{print $1, $3}' test.log abc ide dwf dw3 2、一次輸出多列,某行沒有指定的列,不輸出。第一行沒有第五列 [root@dm shell]# awk '{print $1, $3, $5}' test.log abc ide dwf dw3 w2e 3、awk添加字段,或者將自己的字段與文件中的列相結合 [root@dm shell]# awk '{print $1, $2, "string"}' test.log abc 123 string dwf 23d string [root@dm shell]# awk '{print $1, $2, 666}' test.log abc 123 666 dwf 23d 666 [root@dm shell]# awk '{print "diyilie:"$1, "dierlie:"$2}' test.log diyilie:abc dierlie:123 diyilie:dwf dierlie:23d [root@dm shell]# awk '{print "diyilie:"$1, "666", "dierlie:"$2}' test.log diyilie:abc 666 dierlie:123 diyilie:dwf 666 dierlie:23d
2、使用模式pattern。先介紹兩種特殊模式,BEGIN END。BGEIN模式指定處理文本之前需要執行的操作,END模式表示處理完所有行之后執行的操作。
[root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}' aaa bbb [root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}' test.log aaa bbb [root@dm shell]# awk '{print $1, $2}END{print "ccc","ddd"}' test.log abc 123 dwf 23d ccc ddd [root@dm shell]# awk 'BEGIN{print "aaa", "bbb"}{print $1, $2}END{print "ccc","ddd"}' test.log aaa bbb abc 123 dwf 23d ccc ddd
上述代碼返回的結果就像一個表,有表頭,表內容,表尾。體驗了awk對文本的格式化能力。
awk分隔符:
awk默認以空格作為分隔符,但是這樣描述是不准確的。awk分為輸入分隔符和輸出分隔符兩種。輸入分隔符簡稱FS,默認空白字符(即空格)awk默認以空白字符對每一行進行分割。輸出分隔符,簡稱OFS,awk將每行分割后,輸出到屏幕上以什么字符作為分割。默認的輸出分隔符也是空格。
輸入分隔符
當awk逐行處理文本時,以輸入分隔符為准,將文本切分為多個字段,默認使用空格。如果一段文字里沒有空格,還可以指定特定的字符或符號作為分隔符。我們可以使用 -F 選項,指定 # 作為分隔符。
[root@dm shell]# cat test.log abc#123#ide#wdw dwf#23d#dw3#45f#w2e [root@dm shell]# awk -F# '{print $1, $3}' test.log abc ide dwf dw3
除了使用 -F選項指定分隔符,我們還可以通過FS這個內置變量來指定輸入分隔符。這里后文再詳解。
輸出分隔符
當awk為我們輸出每一列時候,會使用空格隔開每一列,這個空格就是awk默認的輸出分隔符。我們可以使用awk的內置變量OFS設置輸出分隔符,使用變量的時候要配合 -v 選項。示例如下:
[root@dm shell]# awk -F# -v OFS="---" '{print $1, $2}' test.log abc---123 dwf---23d [root@dm shell]# awk -v FS="#" -v OFS="---" '{print $1, $2}' test.log abc---123 dwf---23d
在輸出的時候,如果想將兩列合並到一起。即不使用分隔符。可以省區在字段之間的逗號,這樣輸出的字段就連在一起了。
awk '{print $1 $2}'表示分割后,將第一列和第二列合並到一起輸出
awk '{print $1, $2}'表示分割后,將第一列和第二列以輸出分隔符分開后顯示
awk變量:
awk變量分為內置變量和自定義變量,上文提到的輸入分隔符FS和輸出分隔符OFS都是內置變量。以下列出awk常用的內置變量:
FS : 輸入字段分隔符,默認空白符
OFS : 輸出字段分隔符,默認空白符
RS : 輸入記錄分隔符(輸入換行符),指定輸入時的換行符
ORS : 輸出記錄分隔符(輸出換行符),輸出時用指定符號代替換行符
NF : number of filed 當前行的字段個數(即當前行被分割成幾列)
NR :行號,當前處理文本行的行號
FNR : 各文件分別計數的行號
FILENAME :當前文件名
AGRC :命令行參數的個數
ARGV : 數組,保存命令行所給定的各參數
(1)、內置變量NR、NF,NR表示每一行的行號,NF表示一樣有幾列。NR、NF的使用參考如下示例:
1、文件test.log共有兩行,第一行四列,第二行五列 [root@dm shell]# cat test.log abc 123 ide wdw dwf 23d dw3 45f w2e 2、輸出行號和每行的列數 [root@dm shell]# awk '{print NR, NF}' test.log 1 4 2 5 3、輸出行號和文本內容 [root@dm shell]# awk '{print NR, $0}' test.log 1 abc 123 ide wdw 2 dwf 23d dw3 45f w2e
(2)、內置變量FNR,awk處理多個文件時,如果使用NR顯示行號,那么多個文件的所有行會按照順序進行排序。如果想要分別顯示兩個文件的行號,這時候要用到內置變量FNR,示例如下:
[root@dm shell]# awk '{print NR, $0}' test.log test1.log 1 abc 123 ide wdw 2 dwf 23d dw3 45f w2e 3 abc#123#ide#wdw 4 dwf#23d#dw3#45f#w2ie [root@dm shell]# awk '{print FNR, $0}' test.log test1.log 1 abc 123 ide wdw 2 dwf 23d dw3 45f w2e 1 abc#123#ide#wdw 2 dwf#23d#dw3#45f#w2ie
(3)、內置變量RS,RS是行分隔符。如果不指定,行分隔符就是默認的回車符。如果不想使用默認的回車換行,想使用空格作為換行符。可以理解為妹遇到一個空格就換一行,示例如下:
[root@dm shell]# cat test.log abc 123 ide wdw dwf 23d dw3 45f w2e [root@dm shell]# awk '{print NR, $0}' test.log 1 abc 123 ide wdw 2 dwf 23d dw3 45f w2e [root@dm shell]# awk -v RS=" " '{print NR, $0}' test.log 1 abc 2 123 3 ide 4 wdw dwf 5 23d 6 dw3 7 45f 8 w2e
在指定了空格作為換行符后,awk認為空格換行,但是回車符不再換行。所以在第4行中,人為看到的換行了,但是在awk的世界觀中並沒有換行,都屬於第四行。
(4)、內置變量ORS,輸出行分隔符。默認也是回車符作為輸出行分隔符。如果我們使用+++作為輸出行分隔符,示例如下:
[root@dm shell]# awk -v ORS="+++" '{print NR, $0}' test.log 1 abc 123 ide wdw+++2 dwf 23d dw3 45f w2e+++[root@dm shell]#
把RS和ORS一起使用,在文件中的空格輸入到awk,awk認為是換行符。awk輸出時,認為換行符是+++。可以這么理解,文件里的空格最終輸出后是+++
[root@dm shell]# awk -v RS=" " -v ORS="+++" '{print NR, $0}' test.log 1 abc+++2 123+++3 ide+++4 wdw dwf+++5 23d+++6 dw3+++7 45f+++8 w2e
(5)、內置變量FILENAME,就是顯示文件名,示例如下:
[root@dm shell]# awk '{print FILENAME, FNR, $0}' test.log test1.log test.log 1 abc 123 ide wdw test.log 2 dwf 23d dw3 45f w2e test1.log 1 abc#123#ide#wdw test1.log 2 dwf#23d#dw3#45f#w2ie
(6)、內置變量ARGC和ARGV。ARGV內置變量表示一個數組,這個數組中保存命令行給定的參數,示例如下:
[root@dm shell]# awk 'BEGIN{print "aaa"}' test test1 aaa [root@dm shell]# awk 'BEGIN{print "aaa", ARGV[0], ARGV[1], ARGV[2]}' test test1 aaa awk test test1
所以,ARGV表示的是所有參數組成的數組。第一個參數是awk命令本身,'pattern{action}'不被看作參數。在上邊的例子中,awk test test1 三個參數作為數組元素被放到數組中。而ARGC表示參數的數量,也可以理解為ARGV參數的長度。
[root@dm shell]# awk 'BEGIN{print "aaa", ARGV[0], ARGV[1], ARGV[2], ARGC}' test test1 aaa awk test test1 3
(7)自定義變量,就是用戶定義的變量。有兩種方法。
方法一:-v varname=value 變量名區分大小寫
方法二:在program中直接定義
通過方法一自定義變量,與設置內置變量的方法是一樣的。當我們需要在awk中引用shell中的變量時,可以通過方法一間接引用
1、自定義變量 [root@dm shell]# awk -v myVar="testVar" 'BEGIN{print myVar}' testVar 2、引用shell中變量 [root@dm shell]# abc=6666666 [root@dm shell]# awk -v myVar=$abc 'BEGIN{print myVar}' 6666666
使用方法二定義,直接在程序中定義即可。變量定義和動作之間要用分號隔開,還可以定義多個變量
[root@dm shell]# awk 'BEGIN{myvar="ttt"; print myvar}' ttt [root@dm shell]# awk 'BEGIN{myvar1="aaa"; myvar2="bbb"; print myvar1, myvar2}' aaa bbb
awk格式化能力
在上文中我們已經體驗到了awk的格式化能力。在上文我們通常使用print對文本進行輸出,但是print動作只能進行簡單的文本輸出功能。如果需要改變文本的格式,就需要printf命令。關於printf命令的使用方法請參考(https://www.cnblogs.com/jkin/p/10758802.html)
利用awk的printf動作,即可對文本進行格式化輸出。下邊我們用一個簡單的例子,看一下print動作和printf動作的區別
[root@localhost shell]# clear [root@localhost shell]# cat test aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@localhost shell]# awk '{print $1}' test aaa swe [root@localhost shell]# awk '{printf $1}' test aaaswe[root@localhost shell]#
由例子可以看出,printf動作和printf命令一樣,都不輸出換行符,默認輸出文本在一行里面。但是printf動作既然和printf命令用法一樣,printf動作也肯定由格式替換符了。我們就可以用格式替換符來替換一下$1了,示例如下:
[root@localhost shell]# awk '{printf "%s\n", $1}' test aaa swe
看起來printf動作和printf命令使用方法是一樣的,但是仔細看會發現,printf動作和printf還是存在唯一的不同點。在使用printf動作時,指定的格式與列($1)之間需要使用逗號隔開。在使用printf命令時,指定的格式和傳入的文本不需要使用逗號隔開。示例如下所示:
[root@localhost shell]# awk '{printf "%s\n", $1}' test aaa swe [root@localhost shell]# printf "%s\n" aaa swe aaa swe
其實他們還有一些不同之處,在使用printf命令時,當指定的格式中只有一個格式替換符,但是傳入了多個參數,那么這個參數可以重復使用這一個格式替換符。但是在awk里邊不能這么用,在awk中,格式替換符的數量必須與傳入參數的數量相同。也就是說,格式替換符必須與需要格式化的參數一一對應。示例如下:
[root@localhost shell]# printf "%s\n" 1 2 3 4 1 2 3 4 [root@localhost shell]# awk 'BEGIN{printf "%s\n", 1, 2, 3, 4}' 1 [root@localhost shell]# awk 'BEGIN{printf "%s\n%s\n%s\n%s\n", 1, 2, 3, 4}' 1 2 3 4
總結,在awk中使用printf動作時,需要注意以下幾點
(1)、使用printf動作輸出的文本不會換行,如果需要換行,可以在對應的格式替換符后邊加 \n 進行轉義
(2)、使用printf動作時,指定的格式與被格式化的文本之間,需要用逗號隔開
(3)、使用printf動作時,格式中的格式替換符必須與被格式的文本一一對應
練手小例子:
我們可以利用格式替換符對文本中的每一列進行格式化:
[root@localhost shell]# cat test aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@localhost shell]# awk '{printf "第一列:%s 第二列:%s\n", $1, $2}' test 第一列:aaa 第二列:bbb 第一列:swe 第二列:ef4
上述例子完美的展現了awk對文本格式化的能力。awk本身負責文本切割,printf負責文本格式化。
我們還可以利用awk的begin模式,結合printf動作,輸出一個表格
[root@localhost shell]# awk -v FS=":" 'BEGIN{printf "%-10s\t %s\n", "用戶名稱","用戶ID"} {printf "%-10s\t %s\n", $1,$3}' /etc/passwd 用戶名稱 用戶ID root 0 bin 1 daemon 2 adm 3 lp 4 sync 5 shutdown 6
awk模式解析
在剛開始介紹awk命令的時候,我們已經介紹了兩種模式BEGIN和END。此處,我們將詳細的介紹以下awk中的模式。模式這個詞聽上去不容易被理解,我們這里換一個說法,把模式換成條件。我們知道awk時逐行處理文本的,也就是說,awk處理完當前行,再處理下一行。如果我們不指定任何條件,awk會逐行處理文本中的每一行。但是如果我們指定了條件,只要滿足條件的行會被處理,不滿足條件的行不會被處理。這就是awk中的模式。
當awk處理文本時,會把pattern(模式)作為條件,判斷將要被處理的行是否滿足條件。是否能跟模式進行匹配,如果匹配則進行處理,不匹配不進行處理。我們通過一個例子來理解:
文本test1有三行,第一行有4列,第二行有5列,第三行有2列。
[root@localhost shell]# cat test1 aaa bbb 123 cdf swe ef4 fw2 fer4 fve 111 222 [root@localhost shell]# awk 'NF==5 {print $0}' test1 swe ef4 fw2 fer4 fve
以上例子使用了一個簡單的模式,也可以理解為我們使用了一個條件。這個條件就是如果被處理的行剛好是5列。那被處理的行就滿足條件,滿足條件的行會執行相應的動作。即打印當前行,只要第二行有5列,所以輸出了第二行。舉一反三:
[root@localhost shell]# cat test1 aaa bbb 123 cdf swe ef4 fw2 fer4 fve 111 222 [root@localhost shell]# awk 'NF>2 {print $0}' test1 aaa bbb 123 cdf swe ef4 fw2 fer4 fve [root@localhost shell]# awk 'NF<=4 {print $0}' test1 aaa bbb 123 cdf 111 222 [root@localhost shell]# awk '$1=="aaa" {print $0}' test1 aaa bbb 123 cdf
上面的模式都有一個共同點,就是在上述模式中,都使用了關系運算符。當經過關系運算得出得結果為真時,則滿足條件,然后執行相應得動作。我們總結一下awk支持的關系運算符。
關系運算符 | 含義 | 用法示例 |
< | 小於 | x < y |
<= | 小於等於 | x <= y |
== | 等於 | x == y |
!= | 不等於 | x != y |
>= | 大於等於 | x >= y |
> | 大於 | x > y |
~ | 與對應正則匹配則為真 | x ~/正則/ |
!~ | 與對應正則不匹配則為真 | x !~/正則/ |
我們把用到了關系運算符的模式稱之為:關系表達式模式活着關系運算符模式
其實在學習模式之前,我們一直都在使用模式。之前我們沒有指定模式的時候,其實也是一中模式,稱之為空模式。空模式會匹配文本中的每一行。即每一行都滿足條件。所以每一行都會執行相應的動作。目前位置,我們已經接觸了,空模式、關系運算模式、BEGIN/END模式、這三種。
正則模式
正則模式,顧名思義,正則模式和正則表達式有關。正則模式可以理解為,把正則表達式當條件。能與正則匹配的行,就算滿足條件。滿足條件的行才會執行相應的動作。不能被正則匹配的行,則不會執行相應的動作。我們通過一個例子來理解:
[root@localhost shell]# grep "^ro" /etc/passwd root:x:0:0:root:/root:/bin/bash [root@localhost shell]# awk '/^ro/{print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash
如上例所示,我們通過grep命令配合正則表達式找到了我們所需要的信息。同樣的通過awk命令一樣找到了我們所需要的信息,grep命令和awk命令使用了相同的正則表達式"^ro" 。 唯一的區別時,grep命令直接使用正則表達式,而在awk命令中,正則表達式被放在了兩個斜線中。在上例中,grep看起來更簡單一些,但是awk的優勢在於它強大的格式化能力。
[root@localhost shell]# awk -v FS=":" 'BEGIN{printf "%-10s%10s\n","用戶名","用戶ID"} /^s/{printf "%-15s%10s\n", $1,$3}' /etc/passwd 用戶名 用戶ID sync 5 shutdown 6 systemd-network 192 sshd 74
從這個例子可以看出,該例子使用BGEIN模式生成了表頭,使用正則模式把/etc/passwd文件中以字母s打頭的用戶名和用戶ID篩選出來。使用awk一條命令完成了多項工作。
需要注意的是,在使用正則模式時,如果正則中包含"/",則需要進行轉義,我們通過一個例子來理解。
[root@localhost shell]# grep "/bin/bash$" /etc/passwd root:x:0:0:root:/root:/bin/bash dmdba:x:1000:1000::/home/dmdba:/bin/bash [root@localhost shell]# awk '/\/bin\/bash$/{print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash dmdba:x:1000:1000::/home/dmdba:/bin/bash
如上所示,使用/bin/bash作為shell的用戶被我們找了出來。但是因為正則中包含 / 。awk使用正則模式時,又需要把正則放到兩個 / 中,所以需要轉義。初次之外,還有兩點需要注意:
(1)、在awk命令中使用正則模式時,使用到的正則用法屬於"擴展正則表達式"。
(2)、當使用{x,y}這種次數匹配的正則表達式時,需要配合--posix或者--re-interval選項。(centos7貌似目前沒有這個注意事項了)
行范圍模式
上文中介紹了正則模式,理解了正則模式。再理解行范圍模式,就容易多了。我們通過一個問題來引出行范圍模式。
假設一個文本,內容如下:
[root@localhost shell]# cat -n test3 1 Allen Phillips 2 Green Lee 3 William Aiden James Lee 4 Angel Jack 5 Tyler Kevin 6 Lucas Thomas 7 Kevin
如上所示,Lee這個名字出現了兩次,第一次出現再第二行。Kevin這個名字也出現了兩次,第一次出現在第五行。如果想從上述文本中找出,從Lee第一次出現的行,到Kevin第一次出現的行之間的所有行。我們通過awk的行范圍模式,可以實現以上需求。
[root@localhost shell]# awk '/Lee/,/Kevin/{print $0}' test3 Green Lee William Aiden James Lee Angel Jack Tyler Kevin
我們來解釋一下行范圍模式的語法。
awk '/正則/ {動作}' /some/file
awk '/正則1/,/正則2/ {動作}' /some/file
上邊第一個屬於正則模式的語法,第二個屬於行范圍模式的語法。它表示,從被正則1匹配到的行開始,到被正則2匹配的行結束,之間的所有行都執行相應的動作。所以,這種模式被成為行范圍模式。它對應的是一個范圍內的所有行。需要注意的一點是,在行范圍模式中,不管是正則1還是正則2,都以第一次匹配到的為准。
有時,你可能會有這樣的需求。不想依靠正則表達式去匹配行的特征,想打印第X行到第Y行之間的所有行。這時候其實我們可以通過前邊講的關系運算符模式來實現,示例如下:
[root@localhost shell]# awk 'NR>3 && NR<6 {print $0}' test3 Angel Jack Tyler Kevin
其他
在上文講關系表達式模式時,有一個關系運算符的表格。有一個關系運算符需要和正則配合,它就是 ~ 。我們通過一個例子來理解一下 ~ 和 !~ 運算符。
[root@localhost shell]# cat test4 主機名 網卡1的IP 網卡2的IP 主機A 192.168.2.123 192.168.1.124 主機B 192.168.2.222 172.16.200.2 主機C 10.1.0.1 172.16.100.3 主機D 10.1.2.1 192.168.1.60 主機E 10.1.5.1 172.16.100.5 主機F 192.168.1.234 172.16.100.6 主機G 10.1.7.1 172.16.100.7 [root@localhost shell]# awk '$2~/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1,$2}' test4 主機A 192.168.2.123 主機B 192.168.2.222 主機F 192.168.1.234
上述示例需求是在test4文本中找出,網卡1的IP地址在192.168網段的主機。$2為內置變量,表示文本中第二列。"$2~/正則/"表示文本中第二列如果和正則匹配。則執行對應的動作。
到目前為止,我們已經認識了awk的模式,模式可以分為以下五種
(1)、空模式
(2)、關系運算模式
(3)、正則模式
(4)、行范圍模式
(5)、BEGIN/END模式
awk動作解析
我們從一個小例子來分析動作, awk '{print $0}' file
如上例所示, '{print $0}' 就是我們所說的動作,想必大家一定很熟悉了。該動作的最外層是括號 {} ,內層是print $0。之前我們一直把它當成一個動作。現在我們把它拆開來理解。其實,被查開這兩部分都可以稱之為動作,只不過它們屬於不同類型的動作而已。
print 屬於輸出語句類型的動作。顧名思義,輸出語句類型動作的作用就是輸出,打印信息。print和printf都屬於輸出語句類型的動作。
{}也被稱為動作。 {}屬於組合語句類型的動作,組合語句類型的動作就是將多個代碼組合成代碼塊。我們通過一個示例來理解下:
[root@localhost shell]# cat test5 f s 1 2 2 1 [root@localhost shell]# awk '{print $1} {print $2}' test5 f s 1 2 2 1 [root@localhost shell]# awk '{print $1; print $2}' test5 f s 1 2 2 1
如上所示,當我們使用了兩個大括號。他們屬於組合類型的動作,分別將兩個print括住,表示這兩個print動作分別是獨立的個體。也就是說,一共有四個動作,兩個括號和兩個print。每個大括號中只有一個動作。但是組合語句動作的作用是將多個代碼塊組合成一個整體,如上第三條命令所示,只使用了一個大括號,就可以將兩個print動作組合成一個整體。每段動作之間需要用分號隔開。
除了輸出動作和組合語句動作之外。當然還有其他動作,現在我們認識一下另一種動作,它就是控制語句。第一個控制語句就是條件判斷。也就是編程語法中的if判斷語句。
if(條件) { 語句1 語句2 ... }
在awk中,同樣可以使用if語句進行條件判斷。只不過在命令行的awk中,我們要將上例中的多行語句寫在一行中。示例如下:
[root@localhost shell]# awk '{if(NR==1){print $0}}' test5
f s
上例中即為條件判斷的語法。if(NR==1),NR是內置變量表示行號,所以,該條件表示的是當行號為1時,條件成立。即該動作表示的是打印文本的第一行。
如上例,為什么最外層需要一個大括號?沒有原因,必須這么寫,否則報錯。可以這么理解,所以的動作最外層必須用大括號 {} 括起來。那么if的語法結構里邊也包含大括號。if里邊的大括號是否屬於組合語句?我們可以這么理解,if語句仍然屬於控制語句,控制語句中包含組合語句。if語句的大括號中,也可以執行多個動作,把多個代碼段當成一個整體。也就是說,如果if條件成立,則執行if大括號里的所有動作,示例如下:
[root@localhost shell]# awk '{if(NR==1){print $1; print $2}}' test5
f
s
上例中,如果行號為1,則滿足條件。就會執行if對應大括號的所有代碼。兩個print動作一起執行,如果不成立,兩個動作都不執行。該例中,if對應的大括號里邊有多條語句。所有if語法中的大括號不能省略。如果if對應的大括號里邊只有一條命令,那么if對應的大括號可以省略。
[root@localhost shell]# awk '{if(NR==1)print $0}' test5 f s
如上所示,如果if對應的大括號里邊只有一條命令,那么if對應的大括號可以省略。如果條件成立,需要執行多條語句,大括號不可以省略。上例還可以通過模式NR==1來實現,雖然語法不同,但是結果是一樣的。
編程語言中,除了if判斷,還有if...else..語法和if...else if...else...語法
if(條件) { 語句1; 語句2; ... } else { 語句1; 語句2; ... } if(條件1) { 語句1; 語句2; ... } else if(條件2) { 語句1; 語句2; ... } else { 語句1; 語句2; ... }
其實,這些語法在編程語言中都是相同的。我們直接來看示例:
zai /etc/passwd文件的第三列存在的是用戶ID。在centos6中,ID小於500的是系統用戶,大於500的是普通用戶。centos7中,ID小於1000的是系統用戶,大於1000的是普通用戶。我們以centos7為例,用awk找出哪些是系統用戶,哪些是普通用戶。
[root@localhost shell]# awk -v FS=":" '{if($3<1000){printf "%-10s","系統用戶";print $1}else{printf "%-10s","普通用戶";print $1}}' /etc/passwd 系統用戶 root 系統用戶 bin 系統用戶 daemon 系統用戶 adm 系統用戶 lp 系統用戶 sync 系統用戶 shutdown 系統用戶 halt 系統用戶 mail 系統用戶 operator 系統用戶 games 系統用戶 ftp 系統用戶 nobody 系統用戶 systemd-network 系統用戶 dbus 系統用戶 polkitd 系統用戶 postfix 系統用戶 sshd 系統用戶 chrony 普通用戶 dmdba
我們再看一下 if...else if...else的例子
[root@localhost shell]# cat test6 姓名 年齡 蒼井空 32 吉澤明步 28 櫻井莉亞 18 [root@localhost shell]# awk 'NR!=1 {if($2<20){print $1,"年輕人"}else if($2<30){print $1,"中年人"}else{print($1,"老年人")}}' test6 蒼井空 老年人 吉澤明步 中年人 櫻井莉亞 年輕人
上文我們介紹了控制語句中的條件判斷。除了條件判斷,控制語句還包括循環。awk中也有for循環和while循環,我們先來看一下循環語句的語法:
#for循環語法格式1 for(初始化; 布爾表達式; 更新) { //代碼語句 } #for循環語法格式2 for(變量 in 數組) { //代碼語句 } #while循環語法 while( 布爾表達式 ) { //代碼語句 } #do...while循環語法 do { //代碼語句 }while(條件)
現在我們通過一個示例來了解一下循環語句,因為還沒有介紹數組,我們先演示上述語法中格式一的語法:
[root@localhost shell]# awk 'BEGIN{for(i=1;i<=3;i++){print i}}' 1 2 3
上例中,我們使用了BEGIN模式,BEGIN模式對應的動作中,包含了for循環語句。和其他語言中的for循環幾乎沒什么區別,只不過寫在了一行而已。
再來看一下while的具體使用,為了方便演示,仍然使用BEGIN模式。示例如下:
[root@localhost shell]# awk -v i=1 'BEGIN{while(i<=3){print i;i++}}' 1 2 3 [root@localhost shell]# awk 'BEGIN{i=1;while(i<=3){print i; i++}}' 1 2 3
當while對應的條件滿足時,則執行對應的語句。語句執行完成后,對條件進行修改。
同理,do...while的示例如下,它與while循環的不同之處在於。while循環當滿足條件時才會執行對應語句,而do...while無論條件是否滿足,都會先執行一遍do里邊的代碼。然后再判斷是否滿足while中對應的條件,滿足條件,則執行do對應的代碼。如果不滿足條件,則不再執行do對應的代碼。
[root@localhost shell]# awk 'BEGIN{i=1;do{print "test"; i++}while(i<1)}' test [root@localhost shell]# awk 'BEGIN{do{print "test"; i++}while(i<=3)}' test test test test
如上所示,無論是否滿足while中的條件,都會先執行一遍do對應的代碼。
提到了循環,就必須說說跳出循環的語句。和其他編程語言一樣,在awk中,同樣使用break和continue跳出循環。
continue的作用,跳出當前循環
break的作用,跳出整個循環
我們先來看一個continue的例子,示例如下:
[root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){print i}}' 1 2 3 4 5 [root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){if(i==3){continue};print i}}' 1 2 4 5
由於在for循環中添加了條件判斷,所以當i等於3時。跳過了當前本次循環。沒有執行當前循環需要打印的動作,所以上例中數字3沒有被打印出來。如果想結束的風徹底,可以用break結束循環:
[root@localhost shell]# awk 'BEGIN{for(i=1;i<6;i++){if(i==3){break};print i}}' 1 2
continue和break同樣可以用於while和do...while循環。在shell腳本中,我們肯定用過exit命令。在shell中,exit表示退出當前腳本。在awk中,它的含義是一樣的。它表示不再執行awk命令。相當於退出了當前的awk命令。示例如下:
[root@localhost shell]# awk 'BEGIN{print 1; print 2; print 3}' 1 2 3 [root@localhost shell]# awk 'BEGIN{print 1; exit; print 2; print 3}' 1
如上例所示,在第一條命令中,執行了多個動作。在第二條命令中,也執行了多條動作。但當在awk中執行了exit語句時,之后的動作就不再執行了,想當於退出了awl命令。
其實這樣描述exit並不完全准確,因為,當在AWK中使用了END模式后,exit的作用並不是退出awk命令,而是直接執行END模式中的動作,示例如下:
[root@localhost shell]# cat test7 1 2 3 [root@localhost shell]# awk 'BEGIN{print "start"}{print $0}END{print "over"}' test7 start 1 2 3 over [root@localhost shell]# awk 'BEGIN{print "start"; exit}{print $0}END{print "over"}' test7 start over
如上例所示,在awk命令中使用了END模式后。如果執行了exit語句,那么exit語句之后的動作就不再執行。直接執行END模式中的動作。
在awk中,除了可以使用exit命令結束awk命令。還可以使用next命令結束當前行,什么意思呢?在前邊我們提到,awk時逐行處理文本的。awk會處理完當前行在處理下一行。那么,當awk處理的某行時,我們可以告訴awk這一行不用處理了,直接處理下一行。這時候就可以使用next命令來實現這個需求。換句話說,next命令可以使awk不對當前行執行對應的動作。而是直接處理下一行。示例如下:
[root@localhost shell]# awk '{print $0}' test7 1 2 3 [root@localhost shell]# awk '{if(NR==2){next};print $0}' test7 1 3
其實,next和continue有點類似,只是continue是針對循環而言的,next是針對逐行處理而言的。
到此,awk常用的流程控制語句與循環語句都已經總結完畢了。
awk數組詳解
在其他編程語言中,都有數組的概念。我們可以通過數組的下標,引用數組中的元素。在其他語言中,通常數組的下標都是從0開始。awk也是支持數組的。但是在awk中,數組的下標是從1開始的。但是為了兼容使用習慣。我們也可以從0開始設置下標,到后邊你就會明白,我們先來看一個示例。在其他語言中,一般都需要先聲明一個數組。但是在awk中不需要聲明,直接給數組中得元素賦值即可。示例如下:
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; print huluwa[1]}' 二娃
如上例所示,在BEGIN模式中,存了一個數組。放置了三個元素。想引用第二個元素的值,只要引用下標為1的元素即可。當前我們可以在數組中放置更多的元素。如果命令太長,可能回影響閱讀性。我們可以使用Linux命令行的換行符進行換行,Linux的命令換行符為反斜線 \ 。
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[5]}' 六娃 [root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; print huluwa[5]}'
上例中第二條命令,六娃的本來是隱身。我們把第六個元素設置為空字符串來代表隱身。當打印第六個元素時,打印出的值就是空(注:空格不為空)。舉這個例子,是為了說明在awk中,元素的值可以設置為空,在awk中將元素的值設置為空是合法的。
既然awk中的元素可以為空,那么就不可以根據元素的值是否為空去判斷該元素是否存在了。所以你通過以下辦法來判斷一個元素是否存在是不正確的,示例如下:
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[5]==""){print "第六個元素不存在"}}' 第六個元素不存在
上例中,第六個元素是存在的,但是通過上述方法判斷元素是否存在時,顯示是不存在。
其實,通過空字符串判斷一個元素是否存在之所以不合理,除了上述原因之外。還有一個原因,就是當一個元素不存在與數組時,如果我們直接引用這個不存在的元素。awk會自動創建這個元素,並且默認這個元素為空字符串。示例如下:
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; print huluwa[6]}' [root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; if(huluwa[6]==""){print "第七個元素不存在"}}' 第七個元素不存在 [root@localhost shell]#
如上例第一個命令所示,數組中沒有第七個元素。但是當我們輸出第七個元素時,輸出了空。那么,我們應該如何判斷一個元素在數組中是否存在呢。我們可以使用這樣的語法 if(下標 in 數組名) 。從而判斷數組中是否存在對應的元素。
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; if(5 in huluwa){print "第六個元素存在"}}' 第六個元素存在 [root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; if(6 in huluwa){print "第七個元素存在"}}' [root@localhost shell]#
當然我們還可以使用 ! 對條件進行取反,awk中數組的下標不僅可以是數字,還可以是任意字符串。如果使用過shell中的數組,你可以把awk的數組比作shell中的關聯數組。
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]=""; if(!(6 in huluwa)){print "第七個元素不存在"}}' 第七個元素不存在 [root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa["wuwa"]="五娃"; huluwa[5]=""; print huluwa["wuwa"]}' 五娃 [root@localhost shell]#
其實,awk本身就是關聯數組。最開始以數字最為下標,是因為數字容易理解。awk默認會把數字下標轉換為字符串。所以,它本質上還是一個使用字符串為下標的關聯數組。
使用delete可以刪除數組中的元素,也可以刪除整個數組,示例如下:
[root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[4]; delete huluwa[4]; print huluwa[4]}' 五娃 [root@localhost shell]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; huluwa[5]="六娃"; print huluwa[4]; delete huluwa; print huluwa[1]; print huluwa[3]}' 五娃 [root@localhost shell]#
到目前為止,我們已經介紹了怎么給數組中的元素賦值,輸出某個元素,刪除某個元素等。那么在awk中,想要輸出所有元素呢?我們可以借助前邊提到的for循環語句。我們回顧一下for循環語句
#for循環語法格式1 for(初始化; 布爾表達式; 更新) { //代碼語句 } #for循環語法格式2 for(變量 in 數組) { //代碼語句 }
以上兩種for循環,都可以遍歷數組中的元素。不過第一種for循環只能輸出數字作為下標的數組。如果當數組的下標是無規則的字符串時,我們可以使用第二種for循環方式,示例如下:
[root@localhost DAMENG]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; for(i=0;i<=4;i++){print huluwa[i]}}' 大娃 二娃 三娃 四娃 五娃 [root@localhost DAMENG]# awk 'BEGIN{huluwa["yi"]="大娃"; huluwa["er"]="二娃"; huluwa["san"]="三娃"; huluwa["si"]="四娃";\ huluwa["wu"]="五娃"; for(i in huluwa){print i, huluwa[i]}}' wu 五娃 san 三娃 yi 大娃 er 二娃 si 四娃
如上所示,第一種循環利用for中的變量i和數組中的下標都是數字這一特性,按照順序輸出了數組中的元素值。當數組下標是無規律的字符串時,我們就可以采用第二種方法。在第二種方式中,變量i表示的是元素的下標,並非元素的值。
你一定發現了,當數組中的下標為字符串的時候。元素值得輸出順序與元素在數組中的順序不同。這是因為awk中的數組本身是關聯數組,所以默認打印出是無序的。那么為什么使用數字作為下標就是有序的呢,數字作為下標最終也會轉換為字符串,本質也是關聯數組。那是因為的一種方式變量i為數字,i是按順序遞增的。即使使用數字作為下標,采用第二種for循環,打印出來一樣是無序的。
[root@localhost DAMENG]# awk 'BEGIN{huluwa[0]="大娃"; huluwa[1]="二娃"; huluwa[2]="三娃"; huluwa[3]="四娃";\ huluwa[4]="五娃"; for(i in huluwa){print i, huluwa[i]}}' 4 五娃 0 大娃 1 二娃 2 三娃 3 四娃
上文中,我們都是手動給數組中的元素賦值。那么我們能否將文本中指定的字段分割,將分割后的字段自動復制到數組中呢。答案當然是可以的,但是實現這個功能我們需要借助split函數。函數在下一章節介紹,不過需要說明的是,通過split函數生成的數組下標默認是從1開始的,這就是為什么之前說,awk數組的默認下標是從1開始的了。
實例應用
在實際工作中,我們經常需要使用數組來統計一個字符串出現的次數。比如統計日志中每個IP出現的次數,我們可以使用數組來統計。但是,有時候我們需要使用一些特殊用法,后邊再細聊。
在awk中,我們可以進行數值運算,示例如下:
[root@localhost DAMENG]# awk 'BEGIN{a=1; print a; a=a+1; print a}' 1 2 [root@localhost DAMENG]# awk 'BEGIN{a=1; print a; a++; print a}' 1 2
a的值為1 ,自加后,打印之后a的值增加1。這里a本身就是一個數字,那么如果a是字符串,能否進行自家運算呢,我們試一下:
[root@localhost DAMENG]# awk 'BEGIN{a="test"; print a; a++; print a; a++; print a}' test 1 2 [root@localhost DAMENG]# awk 'BEGIN{a=""; print a; a++; print a; a++; print a}' 1 2
如上所示,在awk中,如果變量為字符串,也可以進行自加運算。如果字符串參與運算,字符串將唄當作數字0進行運算。空字符串一樣在參與運算時,也會被當做數字0。那么如果我們引用一個不存在的元素,並對其進行自加運算的結果如何,來試一下:
[root@localhost DAMENG]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; print arr["ip"]}' 1 [root@localhost DAMENG]# awk 'BEGIN{print arr["ip"]; arr["ip"]++; arr["ip"]++; print arr["ip"]}' 2
如上所示,引用一個不存在的元素時。元素被賦值為一個空字符串,當對這個元素進行自加運算,這個元素就被當成0。加一次就變成了1。利用這個特性,我們可以統計文本中某些字符出現的次數。比如IP地址,示例如下:
[root@localhost shell]# cat test8 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@localhost shell]# awk '{count[$1]++} END{for(i in count){print i, count[i]}}' test8 192.168.1.12 1 192.168.1.1 2 192.168.1.2 3 192.168.1.3 4
上圖中,我們使用了一個空模式,一個END模式。在空模式中,創建了一個數組,並使用數組作為元素的下標。當執行第一行時,我們引用的count("192.168.1.1")。很明顯這個元素並不存在,所以當執行了自加運算后,count("192.168.1.1")被賦值為1。同理,執行第二行。直到再次遇到相同的IP地址時,使用同一個IP地址的元素會再加1。因此,我們統計出每個IP出現的次數。
如果想統計文本中每個人名出現的次數,就可以使用上述套路了:
[root@localhost shell]# cat test3 Allen Phillips Green Lee William Aiden James Lee Angel Jack Tyler Kevin Lucas Thomas Kevin [root@localhost shell]# awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(j in count){print j, count[j]}}' test3 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
關於awk數組的用法,我們先總結這么多。這並不是數組的全部,上邊這么多其實已經夠用了。
awk內置函數
awk中,可以自定義函數,也有內置函數,我們今天來說一下常用的內置函數。awk的內置函數大致可以分為算數函數,字符串函數,時間函數,其他函數等,這里我們介紹一些常用的內置函數。
算數函數
最常用的算數函數有rand函數,srand函數,int函數
可以使用rand函數生成隨機數,使用rand函數時,需要配合srand函數。否則rand函數返回的值將一成不變。
[root@localhost shell]# awk 'BEGIN{print rand()}' 0.237788 [root@localhost shell]# awk 'BEGIN{print rand()}' 0.237788 [root@localhost shell]# awk 'BEGIN{print rand()}' 0.237788
可以看到,如果單純的使用rand函數,生成的值是不變的。可以配合srand函數,生成一個大於0小於1的隨機數。示例如下:
[root@localhost shell]# awk 'BEGIN{srand(); print rand()}' 0.237545 [root@localhost shell]# awk 'BEGIN{srand(); print rand()}' 0.698321 [root@localhost shell]# awk 'BEGIN{srand(); print rand()}' 0.209919
可以看到,上例中生成的隨機數都是小於1 的小數,如果我們想要生成隨機整數。可以將上例中生成的隨機數乘以100,然后截取證書部分,使用int函數可以截取整數部分的值,示例如下:
[root@localhost shell]# awk 'BEGIN{srand();print rand()}' 0.797639 [root@localhost shell]# awk 'BEGIN{srand();print 100*rand()}' 79.3658 [root@localhost shell]# awk 'BEGIN{srand();print int(100*rand())}' 87
字符串函數
我們可以使用gsub函數或者sub函數替換某些文本。
如果我們想把一個文本中的小寫字母l都換成大寫字母L。則可以使用gsub函數,示例如下:
[root@localhost shell]# cat test9 Allen Pjillips Green Lee William Ken Allen [root@localhost shell]# awk '{gsub("l","L",$1); print $0}' test9 ALLen Pjillips Green Lee WiLLiam Ken Allen
如上所示,我們使用gsub函數將小寫字母l替換成大寫字母L。但是替換范圍只局限於$1。所以我們看到只有第一列的小寫字母l替換成了大寫米姆L,其他列並沒有替換。如果像替換文本中所有的小寫字母l,可以把$1換成$0。或者直接省略第三個參數,省略第三個參數時默認是$0。
[root@localhost shell]# awk '{gsub("l","L",$0);print $0}' test9 ALLen PjiLLips Green Lee WiLLiam Ken ALLen [root@localhost shell]# awk '{gsub("l","L");print $0}' test9 ALLen PjiLLips Green Lee WiLLiam Ken ALLen
通過上述例子,應該已經明白,gsub函數會在指定范圍內查找指定的字符,並將其替換為指定字符串。
其實我們呢還可以根據正則表達式,替換字符串,示例如下:
[root@localhost shell]# cat test9 Allen Pjillips Green Lee William Ken Allen [root@localhost shell]# awk '{gsub("[a-z]","6",$1);print $0}' test9 A6666 Pjillips G6666 Lee W666666 Ken Allen
我們再來看一下sub函數,sub函數和gsub函數有什么區別呢。來對比一下:
[root@localhost shell]# awk '{sub("l","L",$1);print $0}' test9 ALlen Pjillips Green Lee WiLliam Ken Allen [root@localhost shell]# awk '{gsub("l","L",$1);print $0}' test9 ALLen Pjillips Green Lee WiLLiam Ken Allen
從示例可以看出,當使用gsub函數時,gsub會替換指定范圍內所有符合條件的字符。當使用sub函數時,sub函數只會替換指定范圍內第一次匹配到的符合條件的字符。我們可以理解gsub是指定范圍內的全局替換,sub是指定范圍內的單次替換。
length函數,獲取指定字符串的長度。示例如下:
[root@localhost shell]# cat test9 Allen Pjillips Green Lee William Ken Allen [root@localhost shell]# awk '{for(i=1;i<=NF;i++){print $i, length($i)}}' test9 Allen 5 Pjillips 8 Green 5 Lee 3 William 7 Ken 3 Allen 5
如上所示,我們輸出了每個字符串的長度。其實,length可以省略傳入的參數,即不知道任何字符。當省略參數時,默認使用$0作為參數。這樣我們就可以使用length函數,獲取到文本每一行的長度。示例如下:
[root@localhost shell]# awk '{print $0, length()}' test9 Allen Pjillips 14 Green Lee 9 William Ken Allen 17
index函數,獲取指定字符位於整個字符串中的位置。示例如下:
[root@localhost shell]# cat test9 Allen Pjillips Green Lee William Ken Allen [root@localhost shell]# awk '{print index($0,"Lee")}' test9 0 7 0
上例中,我們使用index函數,在每一行尋找字符串"Lee"。如果Lee存在於當前行,則返回字符串在當前行的位置。如果不存於當前行則返回0,第二行包含Lee。Lee位於第二行地七個字符的位置,所以返回7。
split函數,將指定字符串按照指定的分隔符切割。
在前邊提到數組時,我們提到可以通過split函數動態生成數組,而不用手動設置數組中每個元素的值。示例如下:
[root@localhost shell]# awk -v ts="大娃:二娃:三娃" 'BEGIN{split(ts,huluwa,":");for(i in huluwa){print huluwa[i]}}' 大娃 二娃 三娃
如上所示,我們通過split函數將字符串ts切割。以 :作為分隔符,將分割后的字符串保存到名為huluwa的數組中。當我們輸出數組時,每個元素的值為分割后的字符。其實,split函數本身也有返回值,其返回值就是分割以后的數組長度,示例如下:
[root@localhost shell]# awk -v ts="大娃:二娃:三娃" 'BEGIN{print split(ts,huluwa,":")}' 3
注意:被split分割后的數組的元素時從下標1開始的,而且數組中輸出的元素可能與字符串中字符的順序不同。原因上邊講數組的時候已經聊過了,如果想要按照順序輸出數組中的元素,可以使用以下方法:
[root@localhost shell]# awk -v ts="qq te ab th" 'BEGIN{split(ts,arr," ");for(i in arr){print arr[i]}}' th qq te ab [root@localhost shell]# awk -v ts="qq te ab th" 'BEGIN{split(ts,arr," ");for(i=1;i<=4;i++){print arr[i]}}' qq te ab th
其他函數
我們還可以通過asort函數根據元素的值進行排序。但是經過asort函數排序過后數組的下標會被重置,示例如下:
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; for(i in t){print i, t[i]}}' a 66 b 88 c 3 [root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t); for(i in t){print i, t[i]}}' 1 3 2 66 3 88
如上所示,數組中的值均為數字。但是下標為自定義的字符串。通過asort函數對數字進行排序后,再次輸出數組中的元素。已經按照元素的值的大小進行了排序,但是數組的下標被重置為了純數字。其實asort還有一種用法,就是對原數組進行排序時,創建一個新數組,把排序后的元素放到新的數組中。這樣就可以保持原數組不唄該改變,只有打印新數組中元素的值,就可以輸出排序后的元素值,示例如下:
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t,newt); for(i in t){print i, t[i]}}' a 66 b 88 c 3 [root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; asort(t,newt); for(i in newt){print i, newt[i]}}' 1 3 2 66 3 88
其實,asort函數本身也有返回值。它的返回值是數組的長度:
[root@localhost shell]# awk 'BEGIN{t["a"]=66; t["b"]=88; t["c"]=3; len=asort(t,newt); for(i=1;i<=len;i++){print i, newt[i]}}' 1 3 2 66 3 88
接下來,我們看一下sorti函數。
使用asort函數可以根據元素的值進行排序,而使用asorti函數可以根據元素的下標進行排序。
當元素的下標為字符串時,可以使用asorti函數,根據下標的字符順序進行排序。如果下標是數字,用for循環就可以排序了。當數組的下標為字符串時,asorti函數根據原數組中的下標的字母順序進行排序,並且講排序后的下標放到一個新的數組中。並且asorti函數會返回新數組的長度。示例如下:
[root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; for(i in t){print i, t[i]}}' z 66 a 3 q 88 [root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; len=asorti(t, newt); for(i=1;i<=len;i++){print i, newt[i]}}' 1 a 2 q 3 z
如上所示,asorti對數組t下標進行排序后,創建一個新的數組newt。既然我們已經得到了數組t的下標,那么就可以根據下標輸出數組t元素的值。從而達到對原數組下標排序后,輸出原數組元素的目的。
[root@localhost shell]# awk 'BEGIN{t["z"]=66; t["q"]=88; t["a"]=3; len=asorti(t, newt); for(i=1;i<=len;i++){print i, newt[i], t[newt[i]]}}' 1 a 3 2 q 88 3 z 66
awk之三元運算和打印奇偶行
三元運算
在前邊介紹if...else結構時,我們有個例子,centos7中,ID小於1000的是系統用戶,大於1000的是普通用戶。我們以centos7為例,用awk找出哪些是系統用戶,哪些是普通用戶。
[root@localhost shell]# awk -v FS=":" '{if($3<1000){usertype="系統用戶"}else{usertype="普通用戶"}; print $1, usertype}' passwd root 系統用戶 bin 系統用戶 dmdba 普通用戶
其實,我們可以通過三元運算,來替換if...else結構。示例如下:
[root@localhost shell]# awk -v FS=":" '{usertype=$3<1000?"系統用戶":"普通用戶"; print $1, usertype}' passwd root 系統用戶 bin 系統用戶 dmdba 普通用戶
如上所示,我們利用三元運算代替了if...else結構。三元運算的語法如下:
條件?結果1 :結果2
如果條件成立,則返回結果1,條件不成立返回結果2。
其實三運運算還有另外一種形式, 表達式1? 表達式2 :表達式3 示例如下:
[root@localhost shell]# awk -v FS=":" '{usertype=$3<1000? a++:b++} END{print a, b}' /etc/passwd 19 1
通過上述命令,統計出了,系統用戶有19個,普通用戶有1個。
打印奇偶行
我們想要使用awk打印文本中的奇數行或偶數行。是很簡單的,我們先來看一下:
[root@localhost shell]# cat test10 第 1 行 第 2 行 第 3 行 第 4 行 第 5 行 第 6 行 第 7 行 [root@localhost shell]# awk 'i=!i' test10 第 1 行 第 3 行 第 5 行 第 7 行 [root@localhost shell]# awk '!(i=!i)' test10 第 2 行 第 4 行 第 6 行
如上所示,我們打印了文本的奇數行和偶數行。想知道原理,需要明白以下兩點
(1)、在awk中,如果省略了模式對應的動作。當前行滿足模式時,默認動作為打印整行。即{print $0}
(2)、在awk中,0或空字符串表示假,非0或者非空字符串表示真
我們來詳細說一下以上兩點。
之前介紹過,模式可以理解為條件。如果當前行與模式匹配,則執行相應的動作。示例如下:
[root@localhost shell]# awk '/1/{print $0}' test10 第 1 行 [root@localhost shell]# awk '$2>5{print $0}' test10 第 6 行 第 7 行 [root@localhost shell]# awk '/1/' test10 第 1 行 [root@localhost shell]# awk '$2>5' test10 第 6 行 第 7 行
由上四個例子,我們發現。如果awk命令中的動作省略,會默認輸出整行
注意:空模式和BEGIN/END模式除外。
第2點,在awk中,0或空字符串表示假,非0或這非空字符串表示真。怎么理解呢,模式可以理解為條件,條件成立則為真,條件不成立則為假。所以模式為真執行相應的動作,模式為假時不執行相應的動作。那么能不能直接把模式替換為真或者假呢,我們試一下:
[root@localhost shell]# awk '{print $0}' test1 test1 test10 test11 [root@localhost shell]# awk '{print $0}' test1 test1 test10 test11 [root@localhost shell]# cat test11 abcd [root@localhost shell]# awk '{print $0}' test11 abcd [root@localhost shell]# awk '1{print $0}' test11 abcd [root@localhost shell]# awk '2{print $0}' test11 abcd [root@localhost shell]# awk '2' test11 abcd [root@localhost shell]# awk '0{print $0}' test11 [root@localhost shell]# awk '0' test11
由上面幾個例子,我們可以得出,只要模式為真,就執行動作,模式為假,不執行動作。其實還可以對模式取非,非真即假,非假即真。
[root@localhost shell]# awk '0' test11 [root@localhost shell]# awk '!0' test11 abcd [root@localhost shell]# awk '5' test11 abcd [root@localhost shell]# awk '!5' test11 [root@localhost shell]#
我們再來延伸以下
[root@localhost shell]# awk 'i=1' test11 abcd
上例中,其實使用了awk的變量,將變量i賦值為1。當i=1以后,i為非零值,表示為真,所以上述例子輸出了所有行。這時候,我們再來看打印奇數行的示例:
[root@localhost shell]# awk 'i=!i' test10 第 1 行 第 3 行 第 5 行 第 7 行
當awk處理第一行時,變量i被初始化,值為空。對空取非,所以此時可以認為模式為真。所以輸出了第一行,同時取非后的值由賦予了變量i。此時i為真,當處理第二行時,對i再次取肥 ,i又變成了假,所以第二行沒有輸出。以此類推就實現了打印奇偶行。