1. 程式人生 > 實用技巧 >六,awk模式(Pattern)之二

六,awk模式(Pattern)之二

  在前文中,我們已經認識了awk的模式,而且,我們已經介紹了awk中的3中模式

  1、空模式

  2、關係運算模式

  3、BEGIN/END模式

  那麼今天,我們就來介紹一下awk的另外兩種常用模式,正則模式與行範圍模式,彆著急,我們一個一個慢慢聊。

  

  正則模式

  先說說什麼是正則模式。

  見名知義,"正則模式"肯定與"正則表示式"有關,所以,如果想要使用這種模式,則必須先學會在Linux中使用正則表示式,如果你對正則表示式還不是特別熟悉,可以參考部落格中的系列文章:在Linux中使用正則表示式

  前文中提到過,"模式"可以理解為"條件",當不指定模式時,文字中的每一行都會執行對應的動作,當指定模式時,只有被模式匹配到的、符合條件的行才會執行對應的動作。

  那麼什麼是正則模式呢?正則模式可以理解為,把"正則表示式"當做"條件",能與正則匹配的行,就算滿足條件,滿足條件的行才會執行對應的動作,不能被正則匹配到的行,則不會執行對應的動作。如果你覺得我說的不明白,來看一個小例子,就能理解。

  不過在進行示例之前,我們先來思考一個小問題。

  我們知道,在Linux中,/etc/passwd檔案中存放了使用者資訊,那麼假設 ,我們想要從/etc/passwd檔案中找出"使用者名稱以zsy開頭"的使用者,我們該怎麼辦呢?

  沒錯,我們可以使用grep命令,配合正則表示式,找出對應的資訊,示例如下。

[root@node1 ~]# grep '^zsy' /etc/passwd
zsy1:x:1003:1003::/home/zsy1:/bin/bash
zsy3:x:1004:1004::/home/zsy3:/bin/bash
zsy2:x:1005:1005::/home/zsy2:/bin/bash

  如上例所示,我們通過grep命令,配合正則表示式,找出了我們需要的資訊,那麼,使用awk命令,能否完成上述需求呢?

  答案是肯定的,那麼我們一起來看看,使用awk命令,怎樣從/etc/passwd檔案中找出使用者名稱以zsy開頭的使用者,示例如下

[root@node1 ~]# awk '/^zsy/{print $0}' /etc/passwd
zsy1:x:1003:1003::/home/zsy1:/bin/bash
zsy3:x:1004:1004::/home/zsy3:/bin/bash
zsy2:x:1005:1005::/home/zsy2:/bin/bash

  聰明如你一定看出來了,不管是使用grep命令,還是使用awk命令,都使用了相同的正則表示式"^zsy"

  唯一的區別就是,在grep命令中,直接使用了正則表示式,而在awk命令中,正則表示式被放入了兩個斜線中。

  這樣說可能不容易理解,看圖說話似乎更加容易理解。

  

  上圖中,awk命令在使用正則表示式時,將正則表示式放入了"/ /"中。

  其實,這就是我們今天要介紹的"正則模式",在使用"正則模式"時,文字行如果能夠被正則表示式匹配到,就會執行對應的動作,如果沒有被正則匹配到,則不會執行對應的動作,而上例中,對應的動作就是{print $0},也就是列印整行,所以,上例中的grep命令與awk命令所實現的效果是完全相同的,那麼你可能會問,既然效果完全相同,為什麼還要使用awk呢?似乎grep更加簡單一些,沒錯,上例中,grep是更加簡單一些,但是不要忘了,awk有自己的優勢,就是格式化能力,那麼,我們換一個場景,可能使用awk就會更加實用了,示例如下。

[root@node1 ~]# awk -v FS=":" 'BEGIN{printf "%-10s\t%s\n","使用者名稱","使用者ID"} /^zsy/{printf "%-10s\t%s\n",$1,$3}' /etc/passwd
使用者名稱       	使用者ID
zsy1      	1003
zsy3      	1004
zsy2      	1005

  猛然一看,上例似乎非常複雜,但是如果你已經掌握了前文中的知識,那麼你一定能夠看明白,上例中藍線標註的部分使用了BEGIN模式,並且格式化輸出了一行文字作為"表頭",上例中紅線標註的部分使用了正則模式,並且格式化輸出了/etc/passwd檔案中的第一列與第三列(使用者名稱欄位與使用者ID欄位),上例中,只使用了awk一條命令就完成了如下多項工作。

  1、從/etc/passwd檔案中找出符合條件的行(使用者名稱以zsy開頭的使用者)。

  2、找出符合條件的文字行以後,以":"作為分隔符,將文字行分段。

  3、取出我們需要的欄位,格式化輸出。

  4、結合BEGIN模式,輸出一個格式化以後的文字,提高可讀性。

  

  因為我們在處理文字時,往往需要用到正則表示式,所以,awk的正則模式應該會經常用到。

  但是需要注意,在使用正則模式時,如果正則中包含"/",則需要進行轉義,這樣說可能不容易理解,我們來看個例子。

  仍然使用/etc/passwd進行測試,我們知道,/etc/passwd中儲存了使用者資訊,其中每行的最後一個欄位為使用者使用的登入shell,假設,我們想要從passwd檔案中找  出使用/bin/bash作為登入shell的使用者,我們該怎麼辦呢?

  沒錯,我們可以使用grep命令,配合正則表示式完成我們的需求,示例如下。

[root@node1 ~]# grep "/bin/bash$" /etc/passwd
root:x:0:0:root:/root:/bin/bash
jack:x:1000:1000::/home/jack:/bin/bash
owen:x:1001:1001::/home/owen:/bin/bash
liuym:x:1002:1002::/home/liuym:/bin/bash
zsy1:x:1003:1003::/home/zsy1:/bin/bash
zsy3:x:1004:1004::/home/zsy3:/bin/bash
zsy2:x:1005:1005::/home/zsy2:/bin/bash

  如上圖所示,使用"/bin/bash"作為登入shell的使用者都被我們找了出來。

  同理,我們使用awk命令,同樣能夠實現與grep相同的效果。

  於是,按照套路,你可能會嘗試如下命令。

[root@node1 ~]# awk '//bin/bash$/{print $0}' /etc/passwd
awk: cmd. line:1: //bin/bash$/{print $0}
awk: cmd. line:1:             ^ unterminated regexp

  正如上圖所示,按照套路,我們將正則部分放入了兩個斜線中,但是執行命令時卻報錯。

  這是因為正則中包含"/",而當使用正則模式時,又需要把正則放入到兩個"/"中。

  所以,我們需要對正則中的"/"進行轉義,轉義後即可正常執行命令,示例如下

  轉義使用字元\即本列中\/即相當於匹配字元/

[root@node1 ~]# awk '/\/bin\/bash$/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
jack:x:1000:1000::/home/jack:/bin/bash
owen:x:1001:1001::/home/owen:/bin/bash
liuym:x:1002:1002::/home/liuym:/bin/bash
zsy1:x:1003:1003::/home/zsy1:/bin/bash
zsy3:x:1004:1004::/home/zsy3:/bin/bash
zsy2:x:1005:1005::/home/zsy2:/bin/bash
[root@node1 ~]# 

  如上圖所示,經過轉義後,awk命令即可正常的匹配到符合正則條件的行,並執行了相應的動作。

  除此之外,還要注意以下兩點

  1、當在awk命令中使用正則模式時,使用到的正則用法屬於"擴充套件正則表示式"

  2、當使用 {x,y} 這種次數匹配的正則表示式時,需要配合--posix選項或者--re-interval選項。

[root@node1 ~]# awk '/he{2,3}y/{print $0}' test3
heey
heeey
[root@node1 ~]# awk --posix '/he{2,3}y/{print $0}' test3
heey
heeey
[root@node1 ~]# awk --re-interval '/he{2,3}y/{print $0}' test3
heey
heeey

  上例中,正則模式中的正則表示式為"he{2,3}y",此表示式表示"hey"中的字母e最少需要連續出現2次,最多隻能連續出現3次,才能被正則表示式匹配到,但是正如上圖所示,沒有使用--posix選項或者--re-interval選項時,awk無法根據正則表示式對文字進行處理,因為上例的正則中包含類似"{x,y}"這樣的次數匹配字元,所以,在使用正則模式時,如果對應的正則表示式中包含類似"{x,y}"這樣的次數匹配字元,則需要使用--posix選項或者--re-interval選項。

  注意:經測試,可以不使用選項--posix或--re-interval

  行範圍模式

  現在聊聊行範圍模式。

  其實,只要理解了正則模式,再理解行範圍模式,就容易多了。

  在介紹行範圍模式之前,先來思考一個小問題,有一個文字檔案,檔案內容如下。

[root@node1 ~]# cat test4
ALlen Phillips
Green Lee
William Aiden James Lee
Angel Jack
Tyler Kevin
Lucas Thomas
Kevin

  如上圖所示,Lee這個名字出現了兩次,第一次出現是在第2行,Kevin這個名字也出現了兩次,第一次出現是在第5行。

  假設我想從上述文字中找出,從Lee第一次出現的行,到Kevin第一次出現的行之間的所有行,我該怎麼辦呢?

  使用awk的行範圍模式,即可完成上述要求,示例如下。

[root@node1 ~]# cat test4
ALlen Phillips
Green Lee
William Aiden James Lee
Angel Jack
Tyler Kevin
Lucas Thomas
Kevin
[root@node1 ~]# awk '/Lee/,/Kevin/{print}' test4
Green Lee
William Aiden James Lee
Angel Jack
Tyler Kevin

  我們來解釋一下,上例中的行範圍模式的語法是什麼意思。

  我們可以把上述行範圍模式的語法與正則模式的語法對比著理解,可能更加方便我們理解。

  

  上圖中第一種語法是正則模式的語法,表示被正則表示式匹配到的行,將會執行對應的動作。

  上圖中第二種語法是行範圍模式的語法,它表示,從被正則1匹配到的行開始,到被正則2匹配到的行結束,之間的所有行都會執行對應的動作,所以,這種模式被稱為行範圍模式,因為它對應的是一個範圍以內的所有行,但是需要注意的是,在行範圍模式中,不管是正則1,還是正則2,都以第一次匹配到的行為準,就像上述示例中,即使Lee在第2行與第3行中都出現了,但是由於正則1先匹配到第2行中的lee,所以,最終打印出的內容從第2行開始,即使Kevin在第5行與第7行中都出現了,但是由於Kevin第一次出現在第5行,所以最終打印出的內容到第5行結束,也就是說,最終打印出了第2行到第5行以內的所有行。

  但是,你可能會有這樣的需求,你不想依靠正則表示式去匹配行的特徵,你只是想單純的打印出從X行到Y行之間的所有行。

  比如,我們有一個文字檔案,這個檔案中一共有7行文字,你想要打印出從第3行到第6行之間的所有行,該怎麼做呢?

  其實,使用之前學習到的"關係運算符模式",即可滿足我們的需求,示例如下。

[root@node1 ~]# cat -n test4
     1	ALlen Phillips
     2	Green Lee
     3	William Aiden James Lee
     4	Angel Jack
     5	Tyler Kevin
     6	Lucas Thomas
     7	Kevin
[root@node1 ~]# awk 'NR>=3 && NR <=6{print}' test4
William Aiden James Lee
Angel Jack
Tyler Kevin
Lucas Thomas

  上圖中,NR為awk的內建變數,表示行號,"NR>=3 && NR<=6"表示行號大於等於3,並且行號小於等於6時,執行對應的動作,而對應的動作就是列印整行,所以,上述命令表示打印出文字中從第3行到第6行之間的所有行。

  你是不是和我一樣,學了新知識,忘了舊知識呢?就像上述示例一樣,你是不是總在嘗試使用"正則模式"解決問題,而忘記了使用"關係表示式模式"解決問題呢?

  其他

  其實,在學習"關係表示式模式"時,有一個"關係運算符"需要與"正則模式"配合使用,它就是"~"

  還記得我們之前總結的一些常用的關係運算符嗎,我們來回顧一下。

  沒錯,細心如你一定發現了,關係運算符"~"與關係運算符"!~"都需要配合"正則模式"使用。

  我們來看一個小示例,就更容易理解了。

  比如,我想要從如下文字中找出,網絡卡1的IP地址在192.168.0.0/16網段內的主機,該怎麼辦呢?

[root@node1 ~]# awk '$2~/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/{print $1,$2}' test5
主機A 192.168.1.123
主機B 192.168.2.222
主機F 192.168.1.234

  上述示例中,$2為awk的內建變數,表示文字中的第2列,"$2~/正則/"表示文字中的第2列如果與正則匹配,則執行對應的動作,對應的動作為"{print $1,$2}",表示列印文字中的第1列與第2列,正則表示式[0-9]{1,3}代表該位為數字長度最小為1位最長為3位

  是不是很簡單?我想你應該明白了。

  到目前為止,我們已經認識了awk的模式,模式可以總結為如下5種。

  1、空模式

  2、關係運算模式

  3、正則模式

  4、行範圍模式

  5、BEGIN/END模式