Linux shell 腳本編程-高級篇 (四)

Linux shell 腳本編程-高級篇 (三)

 

4. 正則表達式


在 shell 腳本中成功運用 sed 編輯器和 gawk 程序的關鍵在於熟練使用正則表達式。這可不是件簡單的事,從大量數據中過濾出特定數據可能會(而且經常會)很複雜。


4.1 什麼是正則表達式



4.1.1 定義


正則表達式是所定義的模式模板(pattern template),Linux 工具可以用它來過濾文本。Linux 工具(比如 sed 編輯器或 gawk 程序)能夠在處理數據時使用正則表達式對數據進行模式匹配。如果數據匹配模式,它就會被接受並進一步處理;如果數據不匹配模式,它就會被濾掉。

正則表達式模式利用通配符來描述數據流中的一個或多個字符。Linux 中有很多場景都可以使用通配符來描述不確定的數據。例如 Linux 的 ls 命令中使用通配符列出文件和目錄:

    [devalone@devalone 19]$ ls -l da*
    -rw-rw-r--. 1 devalone devalone  41 7月  11 12:09 data10.txt
    -rw-rw-r--. 1 devalone devalone  83 7月  11 12:18 data11.txt
    -rw-rw-r--. 1 devalone devalone  52 1月   7 2018 data12.txt
    -rw-rw-r--. 1 devalone devalone 184 1月   7 2018 data1.txt
    -rw-rw-r--. 1 devalone devalone  68 1月   7 2018 data2.txt
    -rw-rw-r--. 1 devalone devalone  21 1月   7 2018 data3.txt
    -rw-rw-r--. 1 devalone devalone  77 1月   7 2018 data4.txt
    -rw-rw-r--. 1 devalone devalone  45 1月   7 2018 data5.txt
    -rw-rw-r--. 1 devalone devalone  92 1月   7 2018 data6.txt
    -rw-rw-r--. 1 devalone devalone 188 7月  11 10:56 data7.txt


星號通配符允許只列出滿足特定條件的文件。

da* 參數會讓 ls 命令只列出名字以 da 開頭的文件。文件名中 da 之後可以有任意多個字符(包括什麼也沒有)。ls命令會讀取目錄中所有文件的信息,但只顯示跟通配符匹配的文件的信息。

正則表達式通配符模式的工作原理與之類似。正則表達式模式含有文本或特殊字符,爲 sed 編輯器和 gawk 程序定義了一個匹配數據時採用的模板。可以在正則表達式中使用不同的特殊字符來定義特定的數據過濾模式。


4.1.2 正則表達式的類型
-----------------------------------------------------------------------------------------------------------------------------------------
使用正則表達式最大的問題在於有不止一種類型的正則表達式。Linux中的不同應用程序可能會用不同類型的正則表達式。這其中包括編程語言(Java、Perl 和 Python)、Linux 實用工具(比如 sed 編輯器、gawk 程序和 grep 工具)以及主流應用(比如 MySQL 和 PostgreSQL 數據庫服務器)。

正則表達式是通過正則表達式引擎(regular expression engine)實現的。正則表達式引擎是一套底層軟件,負責解釋正則表達式模式並使用這些模式進行文本匹配。

在 Linux 中,有兩種流行的正則表達式引擎:

    □ POSIX 基礎正則表達式(basic regular expression,BRE)引擎
    □ POSIX 擴展正則表達式(extended regular expression,ERE)引擎

大多數 Linux 工具都至少符合 POSIX BRE 引擎規範,能夠識別該規範定義的所有模式符號。遺憾的是,有些工具(比如 sed 編輯器)只符合了 BRE 引擎規範的子集。這是出於速度方面的考慮導致的,因爲 sed 編輯器希望能儘可能快地處理數據流中的文本。

POSIX ERE 引擎通常出現在依賴正則表達式進行文本過濾的編程語言中。它爲常見模式提供了高級模式符號和特殊符號,比如匹配數字、單詞以及按字母排序
的字符。gawk 程序用 ERE 引擎來處理它的正則表達式模式。

 

4.2 定義 BRE 模式
-----------------------------------------------------------------------------------------------------------------------------------------
最基本的 BRE 模式是匹配數據流中的文本字符。


4.2.1 純文本
-----------------------------------------------------------------------------------------------------------------------------------------
在 sed 編輯器和 gawk 程序中用標準文本字符串來過濾數據。

示例:
    [devalone@devalone 20]$ echo "This is a test" | sed -n '/test/p'
    This is a test
    [devalone@devalone 20]$ echo "This is a test" | sed -n '/trial/p'
    [devalone@devalone 20]$

    [devalone@devalone 20]$ echo "This is a test" | gawk '/test/{print $0}'
    This is a test
    [devalone@devalone 20]$ echo "This is a test" | gawk '/trial/{print $0}'
    [devalone@devalone 20]$

第一個模式定義了一個單詞 test。sed 編輯器和 gawk 程序腳本用它們各自的 print 命令打印出匹配該正則表達式模式的所有行。由於 echo 語句在文本
字符串中包含了單詞 test,數據流文本能夠匹配所定義的正則表達式模式,因此 sed 編輯器顯示了該行。

第二個模式也定義了一個單詞,這次是 trial。因爲 echo 語句文本字符串沒包含該單詞,所以正則表達式模式沒有匹配,因此 sed 編輯器和 gawk 程序
都沒打印該行。

正則表達式並不關心模式在數據流中的位置。它也不關心模式出現了多少次。一旦正則表達式匹配了文本字符串中任意位置上的模式,它就會將該字符串
傳回 Linux 工具。

關鍵在於將正則表達式模式匹配到數據流文本上。重要的是記住正則表達式對匹配的模式非常挑剔。

第一條原則就是:正則表達式模式都區分大小寫。這意味着它們只會匹配大小寫也相符的模式。

示例:
    [devalone@devalone 20]$ echo "This is a test" | sed -n '/this/p'
    [devalone@devalone 20]$

    [devalone@devalone 20]$ echo "This is a test" | sed -n '/This/p'
    This is a test

第一次嘗試沒能匹配成功,因爲 this 在字符串中並不都是小寫,而第二次嘗試在模式中使用大寫字母,所以能正常工作。

在正則表達式中,不用寫出整個單詞。只要定義的文本出現在數據流中,正則表達式就能夠匹配。

示例:
    [devalone@devalone 20]$ echo "The books are expensive" | sed -n '/book/p'
    The books are expensive

儘管數據流中的文本是 books,但數據中含有正則表達式 book,因此正則表達式模式跟數據匹配。當然,反之正則表達式就不成立了。

示例:
    [devalone@devalone 20]$ echo "The book is expensive" | sed -n '/books/p'
    [devalone@devalone 20]$

完整的正則表達式文本並未在數據流中出現,因此匹配失敗,sed 編輯器不會顯示任何文本。

也不用侷限於在正則表達式中只用單個文本單詞,可以在正則表達式中使用空格和數字。

示例:
    [devalone@devalone 20]$ echo "This is line number 1" | sed -n '/ber 1/p'
    This is line number 1

在正則表達式中,空格和其他的字符並沒有什麼區別。

示例:
    [devalone@devalone 20]$ echo "This is line number1" | sed -n '/ber 1/p'
    [devalone@devalone 20]$

如果在正則表達式中定義了空格,那麼它必須出現在數據流中。甚至可以創建匹配多個連續空格的正則表達式模式。

示例:
    [devalone@devalone 20]$ cat data1
    This is a normal line of test.
    This is  a line with too many space
    [devalone@devalone 20]$ sed -n '/  /p' data1
    This is  a line with too many space

單詞間有兩個空格的行匹配正則表達式模式。這是用來查看文本文件中空格問題的好辦法。


4.2.2 特殊字符
-----------------------------------------------------------------------------------------------------------------------------------------
在正則表達式模式中使用文本字符時,有些事情值得注意。在正則表達式中定義文本字符時有一些特例。有些字符在正則表達式中有特別的含義。

正則表達式識別的特殊字符包括:

    .*[]^${}\+?|()

要記住不能在文本模式中單獨使用這些字符。

如果要用某個特殊字符作爲文本字符,就必須轉義。在轉義特殊字符時,需要在它前面加一個特殊字符來告訴正則表達式引擎應該將接下來的字符當作普通的
文本字符。這個特殊字符就是反斜線(\)。

示例:    如果要查找文本中的美元符,要在它前面加個反斜線 \

    [devalone@devalone 20]$ cat data2
    The cost is $4.00
    [devalone@devalone 20]$ sed -n '/\$/p' data2
    The cost is $4.00

由於反斜線是特殊字符,如果要在正則表達式模式中使用它,必須對其轉義,這樣就產生了兩個反斜線。

示例:
    
    [devalone@devalone 20]$ echo "\ is a special character" | sed -n '/\\/p'
    \ is a special character

儘管正斜線不是正則表達式的特殊字符,但如果它出現在 sed 編輯器或 gawk 程序的正則表達式中,會得到一個錯誤。

    [devalone@devalone 20]$ echo "3 / 2" | sed -n '///p'
    sed: -e expression #1, char 2: No previous regular expression

要使用正斜線,也需要進行轉義。

示例:
    [devalone@devalone 20]$ echo "3 / 2" | sed -n '/\//p'
    3 / 2

現在 sed 編輯器能正確解釋正則表達式模式了。

    

4.2.3 錨字符
-----------------------------------------------------------------------------------------------------------------------------------------
默認情況下,當指定一個正則表達式模式時,只要模式出現在數據流中的任何地方,它就能匹配。有兩個特殊字符可以將模式鎖定在數據流中的行首或行尾。


■ 鎖定在行首
-----------------------------------------------------------------------------------------------------------------------------------------
脫字符(^)定義從數據流中文本行的行首開始的模式。如果模式出現在行首之外的位置,正則表達式模式則無法匹配。

要用脫字符,就必須將它放在正則表達式中指定的模式前面。

示例:
    [devalone@devalone 20]$ echo "The book store" | sed -n '/^book/p'
    [devalone@devalone 20]$
    
    [devalone@devalone 20]$ echo "Books are great" | sed -n '/^Book/p'
    Books are great

脫字符會在每個由換行符決定的新數據行的行首檢查模式。

示例:

    [devalone@devalone 20]$ cat data3
    This is a test line
    this is another test line.
    A line that tests this feature.
    Yet more testing of this

    [devalone@devalone 20]$ sed -n '/^this/p' data3
    this is another test line.
    [devalone@devalone 20]$

只要模式出現在新行的行首,脫字符就能夠發現它。

如果將脫字符放到模式開頭之外的其他位置,那麼它就跟普通字符一樣,不再是特殊字符了:

    [devalone@devalone 20]$ echo "This ^ is a test" | sed -n '/s ^/p'
    This ^ is a test

由於脫字符出現在正則表達式模式的尾部,sed 編輯器會將它當作普通字符來匹配。


■ 鎖定在行尾
-----------------------------------------------------------------------------------------------------------------------------------------
跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定義了行尾錨點。將這個特殊字符放在文本模式之後來指明數據行必須以該文本模式結尾。

示例:
    [devalone@devalone 20]$ echo "This is a good book" | sed -n '/book$/p'
    This is a good book
    [devalone@devalone 20]$ echo "This book is a good" | sed -n '/book$/p'
    [devalone@devalone 20]$

使用結尾文本模式的問題在於必須要留意到底要查找什麼。

示例:
    [devalone@devalone 20]$ echo "There are a lot of good books" | sed -n '/book$/p'
    [devalone@devalone 20]$

將行尾的單詞 book 改成複數形式,就意味着它不再匹配正則表達式模式了,儘管 book 仍然在數據流中。要想匹配,文本模式必須是行的最後一部分。


■ 組合錨點
-----------------------------------------------------------------------------------------------------------------------------------------
在一些常見情況下,可以在同一行中將行首錨點和行尾錨點組合在一起使用。

在第一種情況中,假定要查找只含有特定文本模式的數據行。

示例:

    [devalone@devalone 20]$ cat data4
    this is a test of using both anchors
    I said this is a test
    this is a test
    I'm sure this is a test
    [devalone@devalone 20]$ sed -n '/^this is a test$/p' data4
    this is a test

sed 忽略了那些不單單包含指定的文本的行。

第二種情況乍一看可能有些怪異,但極其有用。將兩個錨點直接組合在一起,之間不加任何文本,這樣過濾出數據流中的空白行。

示例:
    [devalone@devalone 20]$ cat data5
    This is one test line


    This is another test line
    [devalone@devalone 20]$ sed '/^$/d' data5
    This is one test line
    This is another test line

定義的正則表達式模式會查找行首和行尾之間什麼都沒有的那些行。由於空白行在兩個換行符之間沒有文本,剛好匹配了正則表達式模式。sed 編輯器用
刪除命令 d 來刪除匹配該正則表達式模式的行,因此刪除了文本中的所有空白行。這是從文檔中刪除空白行的有效方法。

 

4.2.4 句點字符 (.)
-----------------------------------------------------------------------------------------------------------------------------------------
特殊字符句點字符 (.) 用來匹配除換行符之外的任意單個字符。它必須匹配一個字符,如果在句點字符的位置沒有字符,那麼模式就不成立。

示例:

    [devalone@devalone 20]$ cat data6
    This is a test of line
    THe cat is sleeping
    Tha is a very nice hat
    This test is at line four
    at ten o'clock we'll go home

    [devalone@devalone 20]$ sed -n '/.at/p' data6
    THe cat is sleeping
    Tha is a very nice hat
    This test is at line four

第一行無法匹配,而第二行和第三行就可以。第四行有點複雜。注意,我們匹配了 at,但在 at 前面並沒有任何字符來匹配點號字符。其實是有的!在正則
表達式中,空格也是字符,因此 at 前面的空格剛好匹配了該模式。第五行證明了這點,將 at 放在行首就不會匹配該模式了。


4.2.5 字符組 []
-----------------------------------------------------------------------------------------------------------------------------------------
句點特殊字符在匹配某個字符位置上的任意字符時很有用。但如果想要限定待匹配的具體字符呢?在正則表達式中,這稱爲字符組(character class)。

可以定義用來匹配文本模式中某個位置的一組字符。如果字符組中的某個字符出現在了數據流中,那它就匹配了該模式。

使用方括號 [] 來定義一個字符組。方括號中包含所有希望出現在該字符組中的字符。然後可以在模式中使用整個組,就跟使用其他通配符一樣。

下面是個創建字符組的例子:
    
    [devalone@devalone 20]$ cat data6
    This is a test of line
    THe cat is sleeping
    Tha is a very nice hat
    This test is at line four
    at ten o'clock we'll go home
    
    [devalone@devalone 20]$ sed -n '/[ch]at/p' data6
    THe cat is sleeping
    Tha is a very nice hat

這裏用到的數據文件和句點特殊字符例子中的一樣,但得到的結果卻不一樣。這次我們成功濾掉了只包含單詞 at 的行。匹配這個模式的單詞只有 cat 和
hat。還要注意以 at 開頭的行也沒有匹配。字符組中必須有個字符來匹配相應的位置。

在不太確定某個字符的大小寫時,字符組會非常有用。

示例:
    [devalone@devalone 20]$ echo "Yes" | sed -n '/[Yy]es/p'
    Yes
    [devalone@devalone 20]$ echo "yes" | sed -n '/[Yy]es/p'
    yes

可以在單個表達式中用多個字符組。

示例:
    [devalone@devalone 20]$ echo "Yes" | sed -n '/[Yy]es/p'
    Yes
    [devalone@devalone 20]$ echo "yes" | sed -n '/[Yy]es/p'
    yes
    [devalone@devalone 20]$ echo "yes" | sed -n '/[Yy][Ee][Ss]/p'
    yes
    [devalone@devalone 20]$ echo "Yes" | sed -n '/[Yy][Ee][Ss]/p'
    Yes
    [devalone@devalone 20]$ echo "yeS" | sed -n '/[Yy][Ee][Ss]/p'
    yeS

正則表達式使用了 3 個字符組來涵蓋了3個字符位置含有大小寫的情況。

字符組不必只含有字母,也可以在其中使用數字。

示例:
    [devalone@devalone 20]$ cat data7
    This line doesn't contain a number.
    This line has 1 number on it
    This line a number 2 on it
    This line has number 4 on it

    [devalone@devalone 20]$ sed -n '/[0123]/p' data7
    This line has 1 number on it
    This line a number 2 on it

這個正則表達式模式匹配了任意含有數字0、1、2或3 的行。含有其他數字以及不含有數字的行都會被忽略掉。

可以將字符組組合在一起,以檢查數字是否具備正確的格式,比如電話號碼和郵編。但當嘗試匹配某種特定格式時,必須小心。這裏有個匹配郵編出錯的例子

示例:
    [devalone@devalone 20]$ cat data8
    60633
    46201
    223001
    4353
    22203
    [devalone@devalone 20]$ sed -n '
    > /[0123456789][0123456789][0123456789][0123456789][0123456789]/p
    > ' data8
    60633
    46201
    223001
    22203

它成功過濾掉了不可能是郵編的那些過短的數字,因爲最後一個字符組沒有字符可匹配。但它也通過了那個六位數,儘管只定義了5個字符組。

正則表達式模式可見於數據流中文本的任何位置。經常有匹配模式的字符之外的其他字符。如果要確保只匹配五位數,就必須將匹配的字符和其他字符分開,
要麼用空格,要麼像這個例子中這樣,指明它們就在行首和行尾。

示例:
    [devalone@devalone 20]$ sed -n '
    /^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p
    ' data8
    60633
    46201
    22203

字符組的一個極其常見的用法是解析拼錯的單詞,比如用戶表單輸入的數據。可以創建正則表達式來接受數據中常見的拼寫錯誤。

示例:
    [devalone@devalone 20]$ cat data9
    I need to have some maintenence done on my car.
    I'll pay that in a seperate invoice.
    After I pay for the maintenance my car will be as good as new.

    [devalone@devalone 20]$ sed -n '
    > /maint[ea]n[ae]nce/p
    > /sep[ea]r[ea]te/p
    > ' data9
    I need to have some maintenence done on my car.
    I'll pay that in a seperate invoice.
    After I pay for the maintenance my car will be as good as new.

本例中的兩個 sed 打印命令利用正則表達式字符組來幫助找到文本中拼錯的單詞 maintenance和 separate。同樣的正則表達式模式也能匹配正確拼寫的
maintenance。


4.2.6 排除型字符組 [^]
-----------------------------------------------------------------------------------------------------------------------------------------
在正則表達式模式中,也可以反轉字符組的作用。可以尋找組中沒有的字符,而不是去尋找組中含有的字符。只要在字符組 [] 的開頭加個脫字符 [^]。

示例:
    [devalone@devalone 20]$ cat data6
    This is a test of line
    THe cat is sleeping
    Tha is a very nice hat
    This test is at line four
    at ten o'clock we'll go home

    [devalone@devalone 20]$ sed -n '/[^ch]at/p' data6
    This test is at line four

通過排除型字符組,正則表達式模式會匹配 c或h 之外的任何字符以及文本模式。由於空格字符屬於這個範圍,它通過了模式匹配。但即使是排除,字符組
仍然必須匹配一個字符,所以以 at 開頭的行仍然未能匹配模式。


4.2.7 區間 [-]
-----------------------------------------------------------------------------------------------------------------------------------------
可能注意到了,之前演示郵編的例子的時候,必須在每個字符組中列出所有可能的數字,這實在有點麻煩。好在有一種便捷的方法可以讓人免受這番勞苦。
可以用單破折線符號在字符組中表示字符區間。只需要指定區間的第一個字符、連字符以及區間的最後一個字符就行了。根據 Linux 系統採用的字符集,
正則表達式會包括此區間內的任意字符。

現在可以通過指定數字區間來簡化郵編的例子:

    [devalone@devalone 20]$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
    60633
    46201
    22203

這樣可是節省了不少的鍵盤輸入,每個字符組都會匹配 0~9 的任意數字。如果字母出現在數據中的任何位置,這個模式都將不成立。

示例:
    [devalone@devalone 20]$ echo "a8392" | sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "1839a" | sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "18a32" | sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p'
    [devalone@devalone 20]$

    
同樣的方法也適用於字母。

示例:

    [devalone@devalone 20]$ sed -n '/[c-h]at/p' data6
    THe cat is sleeping
    Tha is a very nice hat

新的模式 [c-h]at 匹配了首字母在字母 c 和字母 h 之間的單詞。這種情況下,只含有單詞 at 的行將無法匹配該模式。

還可以在單個字符組指定多個不連續的區間。

示例:
    [devalone@devalone 20]$ sed -n '/[a-ch-m]at/p' data6
    THe cat is sleeping
    Tha is a very nice hat

該字符組允許區間 a~c、h~m 中的字母出現在 at 文本前,但不允許出現 d~g 的字母。

示例:
    [devalone@devalone 20]$ echo "I'm getting too fat." | sed -n '/[a-ch-m]at/p'
    [devalone@devalone 20]$

該模式不匹配 fat 文本,因爲它沒在指定的區間。


4.2.8 特殊的字符組
-----------------------------------------------------------------------------------------------------------------------------------------
除了定義自己的字符組外,BRE 還包含了一些特殊的字符組,可用來匹配特定類型的字符。下表列出 BRE 特殊的字符組。

    BRE 特殊字符組
    +---------------+---------------------------------------------------------------------------------
    | 組            | 描 述
    +---------------+---------------------------------------------------------------------------------
    | [[:alpha:]]    | 匹配任意字母字符,不管是大寫還是小寫
    +---------------+---------------------------------------------------------------------------------
    | [[:alnum:]]    | 匹配任意字母數字字符0~9、A~Z或a~z
    +---------------+---------------------------------------------------------------------------------
    | [[:blank:]]    | 匹配空格或製表符
    +---------------+---------------------------------------------------------------------------------
    | [[:digit:]]    | 匹配0~9之間的數字
    +---------------+---------------------------------------------------------------------------------
    | [[:lower:]]    | 匹配小寫字母字符a~z
    +---------------+---------------------------------------------------------------------------------
    | [[:print:]]    | 匹配任意可打印字符
    +---------------+---------------------------------------------------------------------------------
    | [[:punct:]]    | 匹配標點符號
    +---------------+---------------------------------------------------------------------------------
    | [[:space:]]    | 匹配任意空白字符:空格、製表符、NL、FF、VT和CR
    +---------------+---------------------------------------------------------------------------------
    | [[:upper:]]    | 匹配任意大寫字母字符A~Z
    +---------------+---------------------------------------------------------------------------------


可以在正則表達式模式中將特殊字符組像普通字符組一樣使用。

示例:
    [devalone@devalone 20]$ echo "abc" | sed -n '/[[:digit:]]/p'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "abc" | sed -n '/[[:alpha:]]/p'
    abc
    [devalone@devalone 20]$ echo "abc123" | sed -n '/[[:alpha:]]/p'
    abc123
    [devalone@devalone 20]$ echo "this is, a test" | sed -n '/[[:punct:]]/p'
    this is, a test
    [devalone@devalone 20]$ echo "this is a test" | sed -n '/[[:punct:]]/p'
    [devalone@devalone 20]$

使用特殊字符組可以很方便地定義區間。可以用[[:digit:]]來代替區間[0-9]。


4.2.9 星號 (*)
-----------------------------------------------------------------------------------------------------------------------------------------
在字符後面放置星號表明該字符必須在匹配模式的文本中出現 0 次或多次。

示例:
    [devalone@devalone 20]$ echo "ik" | sed -n '/ie*k/p'
    ik
    [devalone@devalone 20]$ echo "iek" | sed -n '/ie*k/p'
    iek
    [devalone@devalone 20]$ echo "ieek" | sed -n '/ie*k/p'
    ieek
    [devalone@devalone 20]$ echo "ieeek" | sed -n '/ie*k/p'
    ieeek

這個模式符號廣泛用於處理有常見拼寫錯誤或在不同語言中有拼寫變化的單詞。例如,如果需要寫個可能用在美式或英式英語中的腳本,可以這麼寫:

示例:
    [devalone@devalone 20]$ echo "I'm getting a color TV" | sed -n '/colou*r/p'
    I'm getting a color TV
    [devalone@devalone 20]$ echo "I'm getting a colour TV" | sed -n '/colou*r/p'
    I'm getting a colour TV

模式中的 u* 表明字母u可能出現或不出現在匹配模式的文本中。

類似地,如果知道一個單詞經常被拼錯,可以用星號來允許這種錯誤。

示例:
    [devalone@devalone 20]$ echo "I ate a potatoe with my lunch." | sed -n '/potatoe*/p'
    I ate a potatoe with my lunch.
    [devalone@devalone 20]$ echo "I ate a potato with my lunch." | sed -n '/potatoe*/p'
    I ate a potato with my lunch.

在可能出現的額外字母后面放個星號將允許接受拼錯的單詞。

另一個方便的特性是將句點特殊字符和星號特殊字符組合起來。這個組合能夠匹配任意數量的任意字符。它通常用在數據流中兩個可能相鄰或不相鄰的文本
字符串之間。

示例:
    [devalone@devalone 20]$ echo "this is a regular pattern expression" | sed -n '
    > /regular.*expression/p'
    this is a regular pattern expression

可以使用這個模式輕鬆查找可能出現在數據流中文本行內任意位置的多個單詞。

星號還能用在字符組上。它允許指定可能在文本中出現多次的字符組或字符區間。

示例:
    [devalone@devalone 20]$ echo "bt" | sed -n '/b[ae]*t/p'
    bt
    [devalone@devalone 20]$ echo "bat" | sed -n '/b[ae]*t/p'
    bat
    [devalone@devalone 20]$ echo "bet" | sed -n '/b[ae]*t/p'
    bet
    [devalone@devalone 20]$ echo "btt" | sed -n '/b[ae]*t/p'
    btt
    [devalone@devalone 20]$ echo "baat" | sed -n '/b[ae]*t/p'
    baat
    [devalone@devalone 20]$ echo "baaeeet" | sed -n '/b[ae]*t/p'
    baaeeet
    [devalone@devalone 20]$ echo "baaeeeat" | sed -n '/b[ae]*t/p'
    baaeeeat
    [devalone@devalone 20]$ echo "baakeeet" | sed -n '/b[ae]*t/p'
    [devalone@devalone 20]$

只要 a和 e字符以任何組合形式出現在b和t字符之間(就算完全不出現也行),模式就能夠匹配。如果出現了字符組之外的字符,該模式匹配就會不成立。


4.3 擴展正則表達式
-----------------------------------------------------------------------------------------------------------------------------------------
POSIX ERE 模式包括了一些可供 Linux 應用和工具使用的額外符號。gawk 程序能夠識別 ERE 模式,但 sed 編輯器不能。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    記住,sed 編輯器和 gawk 程序的正則表達式引擎之間是有區別的。gawk 程序可以使用大多數擴展正則表達式模式符號,並且能提供一些額外過濾功能,
    而這些功能都是 sed 編輯器所不具備的。但正因爲如此,gawk 程序在處理數據流時通常才比較慢。


4.3.1 問號 (?)
-----------------------------------------------------------------------------------------------------------------------------------------
問號類似於星號,不過有點細微的不同。問號表明前面的字符可以出現 0 次或 1 次,但只限於此。它不會匹配多次出現的字符。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk '/be?t/{print $0}'
    bt
    [devalone@devalone 20]$ echo "bet" | gawk '/be?t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "beet" | gawk '/be?t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "beeeet" | gawk '/be?t/{print $0}'
    [devalone@devalone 20]$

如果字符 e 並未在文本中出現,或者它只在文本中出現了 1 次,那麼模式會匹配。

與星號一樣,可以將問號和字符組一起使用。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk '/b[ae]?t/{print $0}'
    bt
    [devalone@devalone 20]$ echo "bat" | gawk '/b[ae]?t/{print $0}'
    bat
    [devalone@devalone 20]$ echo "bot" | gawk '/b[ae]?t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "bet" | gawk '/b[ae]?t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "baet" | gawk '/b[ae]?t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "beat" | gawk '/b[ae]?t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "beet" | gawk '/b[ae]?t/{print $0}'
    [devalone@devalone 20]$

如果字符組中的字符出現了 0 次或 1 次,模式匹配就成立。但如果兩個字符都出現了,或者其中一個字符出現了2次,模式匹配就不成立。


4.3.2 加號 (+)
-----------------------------------------------------------------------------------------------------------------------------------------
加號是類似於星號的另一個模式符號,但跟問號也有不同。加號 + 表明前面的字符可以出現 1 次或多次,但必須至少出現 1 次。如果該字符沒有出現,那麼
模式就不會匹配。

示例:
    [devalone@devalone 20]$ echo "beeet" | gawk '/be+t/{print $0}'
    beeet
    [devalone@devalone 20]$ echo "beet" | gawk '/be+t/{print $0}'
    beet
    [devalone@devalone 20]$ echo "bet" | gawk '/be+t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "bt" | gawk '/be+t/{print $0}'
    [devalone@devalone 20]$

如果字符e沒有出現,模式匹配就不成立。

加號同樣適用於字符組,與星號和問號的使用方式相同。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk '/b[ae]+t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "bet" | gawk '/b[ae]+t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "beat" | gawk '/b[ae]+t/{print $0}'
    beat
    [devalone@devalone 20]$ echo "beet" | gawk '/b[ae]+t/{print $0}'
    beet
    [devalone@devalone 20]$ echo "beeat" | gawk '/b[ae]+t/{print $0}'
    beeat

如果字符組中定義的任一字符出現了,文本就會匹配指定的模式。


4.3.3 使用花括號 ({})
-----------------------------------------------------------------------------------------------------------------------------------------
ERE 中的花括號允許爲可重複的正則表達式指定一個上限。這通常稱爲間隔(interval)。

可以用兩種格式來指定區間:

    □ m:正則表達式準確出現 m 次。
    □ m, n:正則表達式至少出現 m 次,至多 n 次。

這個特性可以精確調整字符或字符集在模式中具體出現的次數。

    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    默認情況下,gawk 程序不會識別正則表達式間隔。必須指定 gawk 程序的--re-interval 命令行選項才能識別正則表達式間隔。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk --re-interval '/be{1}t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "beet" | gawk --re-interval '/be{1}t/{print $0}'
    [devalone@devalone 20]$

通過指定間隔爲 1,限定了該字符在匹配模式的字符串中出現的次數。如果該字符出現多次,模式匹配就不成立。

很多時候,同時指定下限和上限也很方便。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}'
    beet
    [devalone@devalone 20]$ echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}'
    [devalone@devalone 20]$

在這個例子中,字符 e 可以出現 1次或 2次,這樣模式就能匹配;否則,模式無法匹配。

間隔模式匹配同樣適用於字符組。

示例:
    [devalone@devalone 20]$ echo "bt" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    bat
    [devalone@devalone 20]$ echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    bet
    [devalone@devalone 20]$ echo "beat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    beat
    [devalone@devalone 20]$ echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    beet
    [devalone@devalone 20]$ echo "beeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "baeaet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
    [devalone@devalone 20]$

如果字母a或e在文本模式中只出現了1~2次,則正則表達式模式匹配;否則,模式匹配失敗。


4.3.4 管道符號 (|)
-----------------------------------------------------------------------------------------------------------------------------------------
管道符號允許在檢查數據流時,用邏輯 OR 方式指定正則表達式引擎要用的兩個或多個模式。如果任何一個模式匹配了數據流文本,文本就通過測試。如果
沒有模式匹配,則數據流文本匹配失敗。

使用管道符號的格式如下:

    expr1|expr2|...

示例:
    [devalone@devalone 20]$ echo "The cat is asleep" | gawk '/cat|dog/{print $0}'
    The cat is asleep
    [devalone@devalone 20]$ echo "The dog is asleep" | gawk '/cat|dog/{print $0}'
    The dog is asleep
    [devalone@devalone 20]$ echo "The sheep is asleep" | gawk '/cat|dog/{print $0}'
    [devalone@devalone 20]$

這個例子會在數據流中查找正則表達式 cat 或 dog。正則表達式和管道符號之間不能有空格,否則它們也會被認爲是正則表達式模式的一部分。

管道符號兩側的正則表達式可以採用任何正則表達式模式(包括字符組)來定義文本。

示例:
    [devalone@devalone 20]$ echo "He has a hat." | gawk '/[ch]at|dog/{print $0}'
    He has a hat.

這個例子會匹配數據流文本中的 cat、hat或dog。


4.3.5 表達式分組 ()
-----------------------------------------------------------------------------------------------------------------------------------------
正則表達式模式也可以用圓括號()進行分組。當將正則表達式模式分組時,該組會被視爲一個標準字符。可以像對普通字符一樣給該組使用特殊字符。

示例:
    [devalone@devalone 20]$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
    Sat
    [devalone@devalone 20]$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
    Saturday

結尾的 urday分組以及問號,使得模式能夠匹配完整的 Saturday或縮寫 Sat。

將分組和管道符號一起使用來創建可能的模式匹配組是很常見的做法。

示例:
    [devalone@devalone 20]$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}'
    cat
    [devalone@devalone 20]$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
    cab
    [devalone@devalone 20]$ echo "bat" | gawk '/(c|b)a(b|t)/{print $0}'
    bat
    [devalone@devalone 20]$ echo "bab" | gawk '/(c|b)a(b|t)/{print $0}'
    bab
    [devalone@devalone 20]$ echo "tab" | gawk '/(c|b)a(b|t)/{print $0}'
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "tac" | gawk '/(c|b)a(b|t)/{print $0}'
    [devalone@devalone 20]$

模式(c|b)a(b|t)會匹配第一組中字母的任意組合以及第二組中字母的任意組合。

 

4.4 正則表達式實戰
-----------------------------------------------------------------------------------------------------------------------------------------


4.4.1 目錄文件計數
-----------------------------------------------------------------------------------------------------------------------------------------
寫個 shell 腳本,對 PATH 環境變量中定義的目錄裏的可執行文件進行計數。

首先將 PATH 變量解析成單獨的目錄名:

    [devalone@devalone 20]$ echo $PATH
    /usr/local/protoc/bin:/home/devalone/programs/apache-tomcat-8.5.11/bin:/usr/local/apache/maven/bin:/usr/local/apache/ant/bin
    :/usr/java/latest/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/hadoop/hadoop/hadoop/bin:/opt/hadoop/hadoop/hadoop/sbin
    :/home/devalone/.local/bin:/home/devalone/bin:/home/devalone/programs/jetty/bin
    :/home/devalone/workspaces/dearall/dearall/runtime/local/bin:/home/devalone/bin/repo

要意識到 PATH 中的每個路徑由冒號分隔。要獲取可在腳本中使用的目錄列表,就必須用空格來替換冒號。sed 編輯器用一條簡單表達式就能完成替換工作。

    [devalone@devalone 20]$ echo $PATH | sed 's/:/ /g'

分離出目錄之後,就可以使用標準 for 語句中來遍歷每個目錄:

    mypath=$(echo $PATH | sed 's/:/ /g')
    for directory in $mypath
    do
    ...
    done

一旦獲得了單個目錄,就可以用 ls 命令來列出每個目錄中的文件,並用另一個 for 語句來遍歷每個文件,爲文件計數器增值。

最終腳本如下:

    [devalone@devalone 20]$ cat countfiles.sh
    #!/bin/bash
    # count number of files in your PATH
    mypath=$(echo $PATH | sed 's/:/ /g')
    count=0
    for directory in $mypath
    do
            check=$(ls $directory)
            for item in $check
            do
                    count=$[ $count + 1 ]
            done
            echo "$directory - $count"
            count=0
    done

運行:
    [devalone@devalone 20]$ ./countfiles.sh
    /usr/local/protoc/bin - 1
    /home/devalone/programs/apache-tomcat-8.5.11/bin - 23
    /usr/local/apache/maven/bin - 6
    /usr/local/apache/ant/bin - 13
    /usr/java/latest/bin - 49
    /usr/local/bin - 4
    /usr/bin - 1909
    /usr/local/sbin - 0
    /usr/sbin - 687
    /opt/hadoop/hadoop/hadoop/bin - 11
    /opt/hadoop/hadoop/hadoop/sbin - 29
    /home/devalone/.local/bin - 0
    /home/devalone/bin - 1
    /home/devalone/programs/jetty/bin - 0
    /home/devalone/workspaces/dearall/dearall/runtime/local/bin - 2
    /home/devalone/bin/repo - 1


4.4.2 驗證電話號碼
-----------------------------------------------------------------------------------------------------------------------------------------
電話號碼有幾種常見的形式:

    (123)456-7890
    (123) 456-7890
    123-456-7890
    123.456.7890

這樣用戶在表單中輸入的電話號碼就有 4 種可能。正則表達式必須足夠強大,才能處理每一種情況。

在構建正則表達式時,最好從左手邊開始,然後構建用來匹配可能遇到的字符的模式。在這個例子中,電話號碼中可能有也可能沒有左圓括號。可以用如下
模式來匹配:
    
    ^\(?
    
脫字符用來表明數據的開始。由於左圓括號是個特殊字符,因此必須將它轉義成普通字符。問號表明左圓括號可能出現,也可能不出現。

緊接着就是 3 位區號。在美國,區號以數字 2 開始(沒有以數字 0或 1開始的區號),最大可到 9。要匹配區號,可以用如下模式:

    [2-9][0-9]{2}

這要求第一個字符是 2~9 的數字,後跟任意兩位數字。

在區號後面,收尾的右圓括號可能存在,也可能不存在。

    \)?

在區號後,存在如下可能:有一個空格,沒有空格,有一條單破折線或一個點。可以對它們使用管道符號,並用圓括號進行分組:

    (| |-|\.)

第一個管道符號緊跟在左圓括號後,用來匹配沒有空格的情形。必須將句點字符轉義,否則它會被解釋成可匹配任意字符。

緊接着是 3 位電話交換機號碼。這裏沒什麼需要特別注意的:

    [0-9]{3}

在電話交換機號碼之後,必須匹配一個空格、一條單破折線或一個點(這次不用考慮匹配沒有空格的情況,因爲在電話交換機號碼和其餘號碼間必須有至少
一個空格):

    ( |-|\.)
    
最後,必須在字符串尾部匹配 4 位本地電話分機號:

    [0-9]{4}$

完整的模式如下:

    ^\(?[2-9][0-9]{2}\)?(| |-|\.)[0-9]{3}( |-|\.)[0-9]{4}$
    
可以在 gawk 程序中用這個正則表達式模式來過濾掉不符合格式的電話號碼。

創建一個簡單腳本,在腳本中調用 gawk 程序來使用該正則表達式,然後用這個腳本來過濾的電話薄。

腳本如下:

    [devalone@devalone 20]$ cat isphone.sh
    #!/bin/bash
    # script to filter out bad phone numbers
    gawk --re-interval '/^\(?[2-9][0-9]{2}\)?(| |-|\.)[0-9]{3}( |-|\.)[0-9]{4}$/{print $0}'

運行:

    [devalone@devalone 20]$ echo "317-555-1234" | ./isphone.sh
    317-555-1234
    [devalone@devalone 20]$ echo "000-555-1234" | ./isphone.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "312-555-1234" | ./isphone.sh
    312-555-1234

或者將含有電話號碼的整個文件重定向到腳本來過濾掉無效的號碼:

    [devalone@devalone 20]$ cat phonelist
    000-000-0000
    123-456-7890
    212-555-1234
    (317)555-1234
    (202) 555-1234
    33523
    1234567890
    234.123.4567
    
    [devalone@devalone 20]$ cat phonelist | ./isphone.sh
    212-555-1234
    (317)555-1234
    (202) 555-1234
    234.123.4567

只有匹配該正則表達式模式的有效電話號碼纔會出現。


4.4.3 解析郵件地址
-----------------------------------------------------------------------------------------------------------------------------------------
郵件地址的基本格式爲:

    username@hostname

username 值可用字母數字字符以及以下特殊字符:

    □ 點號 (.)
    □ 連字符 (-)
    □ 加號 (+)
    □ 下劃線 (_)

在有效的郵件用戶名中,這些字符可能以任意組合形式出現。

郵件地址的 hostname 部分由一個或多個域名和一個服務器名組成。服務器名和域名也必須遵照嚴格的命名規則,只允許字母數字字符以及以下特殊字符:

    □ 點號 (.)
    □ 連字符 (-)
    □ 下劃線 (_)
    
服務器名和域名都用點分隔,先指定服務器名,緊接着指定子域名,最後是後面不帶點號的頂級域名。

從左側開始構建這個正則表達式模式。用戶名中可以有多個有效字符:

    ^([a-zA-Z0-9_\-\.\+]+)@

這個分組指定了用戶名中允許的字符,加號表明必須有至少一個字符。下一個字符很明顯是 @,沒什麼意外的。

hostname 模式使用同樣的方法來匹配服務器名和子域名:

    ([a-zA-Z0-9_\-\.]+)

這個模式可以匹配如下文本:

    server
    server.subdomain
    server.subdomain.subdomain

對於頂級域名,有一些特殊的規則。頂級域名只能是字母字符,必須不少於二個字符(國家或地區代碼中使用),並且長度上不得超過五個字符。下面就是
頂級域名用的正則表達式模式:

    \.([a-zA-Z]{2,5})$

將整個模式放在一起會生成如下模式:

    ^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$

這個模式會從數據列表中過濾掉那些格式不正確的郵件地址。現在可以創建腳本來實現這個正則表達式:

    [devalone@devalone 20]$ cat isemail.sh
    #!/bin/bash
    # script to filter out bad emails
    gawk --re-interval '/^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/{print $0}'

運行:
    [devalone@devalone 20]$ echo "[email protected]" | ./isemail.sh
    [email protected]
    [devalone@devalone 20]$ echo "[email protected]." | ./isemail.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "[email protected]" | ./isemail.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "rich@here-now" | ./isemail.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "[email protected]" | ./isemail.sh
    [email protected]
    [devalone@devalone 20]$ echo "[email protected]" | ./isemail.sh
    [email protected]
    [devalone@devalone 20]$ echo "deva/[email protected]" | ./isemail.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "deva#[email protected]" | ./isemail.sh
    [devalone@devalone 20]$
    [devalone@devalone 20]$ echo "deva*[email protected]" | ./isemail.sh
    [devalone@devalone 20]$

 

系列目錄:

Linux shell 腳本編程-高級篇 (一)

Linux shell 腳本編程-高級篇 (二)

Linux shell 腳本編程-高級篇 (三)

Linux shell 腳本編程-高級篇 (四)

Linux shell 腳本編程-高級篇 (五)

Linux shell 腳本編程-高級篇 (六)

Linux shell 腳本編程-高級篇 (七)

 

 

-----------------------------------------------------------------------------------------------------------------------------------------
參考:

    《Linux 命令行與 shell 腳本編程大全》 第 3 版 —— 2016.8(美)Richard Blum  Cristine Bresnahan

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章