Linux awk命令詳解


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又變成了假,所以第二行沒有輸出。以此類推就實現了打印奇偶行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM