《Linux命令行與shell腳本編程大全》,4 E -- Chapter 20
一、 什么是正則表達式
1. 定義
- 正則表達式是你所定義的模式模板(pattern template),Linux工具可以用它來過濾文本。Linux工具(比如sed編輯器或gawk程序)能夠在處理數據時使用正則表達式對數據進行模式匹配。如果數據匹配模式,它就會被接受并進一步處理;如果數據不匹配模式,它就會被濾掉。圖20-1描述了這個過程。
- 正則表達式模式利用通配符來描述數據流中的一個或多個字符。Linux中有很多場景都可以使用通配符來描述不確定的數據。你已經看到過在Linux的 ls 命令中使用通配符列出文件和目錄的例子(參見第3章)。星號通配符允許你只列出滿足特定條件的文件,例如:
$ ls -al da*
-rw-r--r-- 1 rich rich 45 Nov 26 12:42 data
-rw-r--r-- 1 rich rich 25 Dec 4 12:40 data.tst
-rw-r--r-- 1 rich rich 180 Nov 26 12:42 data1
-rw-r--r-- 1 rich rich 45 Nov 26 12:44 data2
-rw-r--r-- 1 rich rich 73 Nov 27 12:31 data3
-rw-r--r-- 1 rich rich 79 Nov 28 14:01 data4
-rw-r--r-- 1 rich rich 187 Dec 4 09:45 datatest
-
da*
參數會讓ls
命令只列出名字以da
開頭的文件。文件名中da之后可以有任意多個字 符(包括什么也沒有),ls
命令會讀取目錄中所有文件的信息,但只顯示跟通配符匹配的文件的信息。正則表達式通配符模式的工作原理與之類似。正則表達式模式含有文本或特殊字符,為sed編輯器和gawk程序定義了一個匹配數據時采用的模板。可以在正則表達式中使用不同的特殊字符來定義特定的數據過濾模式。
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)引擎
- 由于實現正則表達式的方法太多,很難用一個簡潔的描述來涵蓋所有可能的正則表達式。
二、 定義 BRE 模式
- 最基本的BRE模式是匹配數據流中的文本字符。本節將會演示如何在正則表達式中定義文本
以及會得到什么樣的結果。
1. 純文本
- 第18章演示了如何在sed編輯器和gawk程序中用標準文本字符串來過濾數據。通過下面的例
子來復習一下。
$ echo "This is a test" | sed -n '/test/p'
This is a test
$·· echo "This is a test" | sed -n '/trial/p'
$ echo "This is a test" | gawk '/test/{print $0}'
This is a test
$ echo "This is a test" | gawk '/trial/{print $0}
- 第一個模式定義了一個單詞test。sed編輯器和gawk程序腳本用它們各自的 print 命令打印出匹配該正則表達式模式的所有行。由于 echo 語句在文本字符串中包含了單詞test,數據流文本能夠匹配所定義的正則表達式模式,因此sed編輯器顯示了該行。
- 第二個模式也定義了一個單詞,這次是trial。因為 echo 語句文本字符串沒包含該單詞,所以正則表達式模式沒有匹配,因此sed編輯器和gawk程序都沒打印該行。
- 正則表達式并不關心模式在數據流中的位置。它也不關心模式出現了多少次。一旦正則表達式匹配了文本字符串中任意位置上的模式,它就會將該字符串傳回Linux工具。關鍵在于將正則表達式模式匹配到數據流文本上。
- 重要的是記住正則表達式對匹配的模式非常挑剔。第一條原則就是:正則表達式模式都區分大小寫。這意味著它們只會匹配大小寫也相符的模式。
$ echo "This is a test" | sed -n '/this/p'
$ echo "This is a test" | sed -n '/This/p'
This is a test
- 在正則表達式中,你不用寫出整個單詞。只要定義的文本出現在數據流中,正則表達式就能夠匹配。
$ echo "The books are expensive" | sed -n '/book/p'
The books are expensive
-
不用局限于在正則表達式中只用單個文本單詞,可以在正則表達式中使用空格和數字
單詞間有兩個空格的行匹配正則表達式模式。這是用來查看文本文件中空格問題的好辦法。
$ cat data1
This is a normal line of text.
This is a line with too many spaces.
$ sed -n '/ /p' data1
This is a line with too many spaces.
2. 特殊字符
在正則表達式中定義文本字符時,有一些特例。有些字符在正則表達式中有特別的含義。如果要在文本模式中使用這些字符,結果會超出你的意料。
-
正則表達式識別的特殊字符包括:
·.*[]^${}\+?|(
不能在文本模式中單獨使用這些字符,如果要用某個特殊字符作為文本字符,就必須轉義符號反斜線(\)。轉義字符來告訴正則表達式引擎應該將接下來的字符當作普通的文本字符。
如果要查找文本中的美元符,只要在它前面加個反斜線。
$ cat data2
The cost is $4.00
$ sed -n '/\$/p' data2
The cost is $4.00
- 由于反斜線是特殊字符,如果要在正則表達式模式中使用它,你必須對其轉義,這樣就產生
了兩個反斜線。
$ echo "\ is a special character" | sed -n '/\\\/p'
\ is a special character
- 盡管正斜線不是正則表達式的特殊字符,但如果它出現在sed編輯器或gawk程序的正則表達式中,你就會得到一個錯誤。要使用正斜線,也需要進行轉義。
$ echo "3 / 2" | sed -n '///p'
sed: -e expression #1, char 3: unknown command: `/'
$ echo "3 / 2" | sed -n '/\//p'
3 / 2
3. 錨字符
- 默認情況下,當指定一個正則表達式模式時,只要模式出現在數據流中的任何地方,它就能匹配。有兩個特殊字符可以用來將模式鎖定在數據流中的行首或行尾。
3.1 鎖定在行首
- 脫字符(^)定義從數據流中文本行的行首開始的模式。如果模式出現在行首之外的位置,正則表達式模式則無法匹配。
$ echo "The book store" | sed -n '/^ book/p'
$ echo "Books are great" | sed -n '/^ Book/p'
Books are great
- 脫字符會在每個由換行符決定的新數據行的行首檢查模式。
$ cat data3
This is a test line.
this is another test line.
A line that tests this feature.
Yet more testing of this
$ sed -n '/^ this/p' data3
this is another test line.
- 如果你將脫字符放到模式開頭之外的其他位置,那么它就跟普通字符一樣,不再是特殊字符了:
$ echo "This ^ is a test" | sed -n '/s ^ /p'
This ^ is a test
說明 :如果指定正則表達式模式時只用了脫字符,就不需要用反斜線來轉義。但如果你在模式中先指定了脫字符,隨后還有其他一些文本,那么你必須在脫字符前用轉義字符。
3.2 鎖定在行尾
- 跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定義了行尾錨點。將這個特殊字符放在文本模式之后來指明數據行必須以該文本模式結尾。
echo "This is a good book" | sed -n '/book$/p'
This is a good book
echo "This book is good" | sed -n '/book$/p'
3.3 組合錨點
? 一些常見情況下,可以在同一行中將行首錨點和行尾錨點組合在一起使用。
-
^模式$
,查找只含有特定文本模式的數據行。
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.
$ sed -n '/^this is a test$/p' data4
this is a test
-
^$
,將兩個錨點直接組合在一起,之間不加任何文本,這樣過濾出數據流中的空白行
$ cat data5
This is one test line.
This is one test line.
This is another test line.
sed '/^ $/d' data5
This is one test line.
This is another test line.
? 定義的正則表達式模式會查找行首和行尾之間什么都沒有的那些行。由于空白行在兩個換行符之間沒有文本,剛好匹配了正則表達式模式。sed編輯器用刪除命令 d 來刪除匹配該正則表達式模式的行,因此刪除了文本中的所有空白行。這是從文檔中刪除空白行的有效方法。
3.4 點號字符
- 特殊字符點號用來匹配除換行符之外的任意單個字符。它必須匹配一個字符,如果在點號字符的位置沒有字符,那么模式就不成立。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
$ sed -n '/.at/p' data6
The cat is sleeping.
That is a very nice hat.
This test is at line four.
? 行有點復雜。注意,我們匹配了 at ,但在 at 前面并沒有任何字符來匹配點號字符。其實是有的!在正則表達式中,空格也是字符,因此 at 前面的空格剛好匹配了該模式。第五行證明了這點,將 at 放在行首就不會匹配該模式了。
3.4 字符組
- 點號特殊字符在匹配某個字符位置上的任意字符時很有用。但如果你想要限定待匹配的具體字符呢?在正則表達式中,這稱為字符組(character class)。可以定義用來匹配文本模式中某個位置的一組字符。如果字符組中的任意一個字符出現在了數據流中,那它就匹配了該模式。
- 使用方括號來定義一個字符組。方括號中包含所有你希望出現在該字符組中的字符。然后你可以在模式中使用整個組,就跟使用其他通配符一樣。這需要一點時間來適應,但一旦你適應了,效果可是令人驚嘆的。
下面是個創建字符組的例子。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
$ sed -n '/[ch]at/p' data6
The cat is sleeping.
That is a very nice hat.
? 首先篩選出了含有at的行,但不包含at ten o'clock we'll go home.
這一行,因為這一行的at前面沒有字符,而該模式規定除了匹配at字符之外,還要匹配s字符或h字符,因此at ten o'clock we'll go home.
淘汰,The cat is sleeping.
和That is a very nice hat.
通過。
- 在不太確定某個字符的大小寫時,字符組會非常有用。
$ echo "Yes" | sed -n '/[Yy]es/p'
Yes
$ echo "yes" | sed -n '/[Yy]es/p'
yes
- 也可以在單個表達式中用多個字符組。
$ echo "Yes" | sed -n '/[Yy][Ee][Ss]/p'
Yes
$ echo "yEs" | sed -n '/[Yy][Ee][Ss]/p'
yEs
$ echo "yeS" | sed -n '/[Yy][Ee][Ss]/p'
yeS
- 在不太確定某個字符的大小寫時,字符組會非常有用。
$ echo "Yes" | sed -n '/[Yy]es/p'
Yes
$ echo "yes" | sed -n '/[Yy]es/p'
yes
- 字符組不必只含有字母,也可以在其中使用數字。
$ 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 a number 4 on it.
$ sed -n '/[0123]/p' data7
This line has 1 number on it.
This line a number 2 on it.
? 這個正則表達式模式匹配了任意含有數字0、1、2或3的行。含有其他數字以及不含有數字的行都會被忽略掉。
-
可以將字符組組合在一起,以檢查數字是否具備正確的格式。
比如我們的郵編是六位的數字,那么下面的命令就可以把不是六位的給篩選掉`
$ cat data8
60633
46201
223001
4353
22203
$ sed -n '/^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p' data8
60633
46201
22203
- 字符組的一個極其常見的用法是解析拼錯的單詞,比如用戶表單輸入的數據。可以創建正則表達式來接受數據中常見的拼寫錯誤。
$ 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.
#換行能不能代替為空格?
$ 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。
3.5 排除型字符組
- 在正則表達式模式中,也可以反轉字符組的作用。可以尋找組中沒有的字符,而不是去尋找組中含有的字符。要這么做的話,只要在字符組的開頭加個脫字符。
$ cat data6
This is a test of a line.
The cat is sleeping.
That is a very nice hat.
This test is at line four.
at ten o'clock we'll go home.
This test is at line four.
$ sed -n '/[^ ch]at/p' data6
? 首先篩選出含有at的行,之后在這些行必須還要滿足條件:at的前面沒有(空格)或
c
或h
,所以三者均被排除。
3.5 區間
? 之前演示郵編的例子的時候,必須在每個字符組中列出所有可能的數字,這實在有點麻煩。好在有一種便捷的方法可以讓人免受這番勞苦。可以用單破折線符號在字符組中表示字符區間。只需要指定區間的第一個字符單破折線以及區間的最后一個字符就行了。
- 根據Linux系統采用的字符集(參見第2章),正則表達式會包括此區間內的任意字符。現在你可以通過指定數字區間來簡化郵編的例子。
$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
60633
46201
45902
- 同樣的方法也適用于字母。
$ sed -n '/[c-h]at/p' data6
The cat is sleeping.
That is a very nice hat.
? 新的模式 [c-h]at 匹配了首字母在字母c和字母h之間的單詞。這種情況下,只含有單詞 at的行將無法匹配該模式。
- 還可以在單個字符組指定多個不連續的區間。
$ sed -n '/[a-ch-m]at/p' data6
The cat is sleeping.
That is a very nice hat.
? 該字符組允許區間ac、hm中的字母出現在 at 文本前,但不允許出現d~g的字母。
$ echo "I'm getting too fat." | sed -n '/[a-ch-m]at/p'
? 該模式不匹配 fat 文本,因為它沒在指定的區間。
3.6 特殊的字符組
- 除了定義自己的字符組外,BRE還包含了一些特殊的字符組,可用來匹配特定類型的字符。
-
表20-1介紹了可用的BRE特殊的字符組。
image.png - 可以在正則表達式模式中將特殊字符組像普通字符組一樣使用。
$ echo "abc" | sed -n '/[[:digit:]]/p'
$ echo "abc" | sed -n '/[[:alpha:]]/p'
abc
$ echo "abc123" | sed -n '/[[:digit:]]/p'
abc123
$ echo "This is, a test" | sed -n '/[[:punct:]]/p'
This is, a test
$ echo "This is a test" | sed -n '/[[:punct:]]/p'
- 使用特殊字符組可以很方便地定義區間。可以用
[[:digit:]]
來代替區間[0-9]。
3.7 星號
- 在字符后面放置星號表明該字符必須在匹配模式的文本中出現0次或多次,即可出現可不出現。
$ echo "ik" | sed -n '/ie*k/p'
ik
$ echo "iek" | sed -n '/ie*k/p'
iek
$ echo "ieek" | sed -n '/ie*k/p'
ieek
$ echo "ieeek" | sed -n '/ie*k/p'
ieeek
- 這個模式符號廣泛用于處理有常見拼寫錯誤或在不同語言中有拼寫變化的單詞。舉個例子,如果需要寫個可能用在美式或英式英語中的腳本,可以這么寫:
$ echo "I'm getting a color TV" | sed -n '/colou*r/p'
I'm getting a color TV
$ echo "I'm getting a colour TV" | sed -n '/colou*r/p'
I'm getting a colour TV
? 模式中的 u* 表明字母u可能出現或不出現在匹配模式的文本中。
- 類似地,如果你知道一個單詞經常被拼錯,你可以用星號來允許這種錯誤。
$ echo "I ate a potatoe with my lunch." | sed -n '/potatoe*/p'
I ate a potatoe with my lunch.
echo "I ate a potato with my lunch." | sed -n '/potatoe*/p'
I ate a potato with my lunch.
? 在可能出現的額外字母后面放個星號將允許接受拼錯的單詞。
- 另一個方便的特性是將點號特殊字符和星號特殊字符組合起來。這個組合能夠匹配任意數量的任意字符。它通常用在數據流中兩個可能相鄰或不相鄰的文本字符串之間。
$ echo "this is a regular pattern expression" | sed -n '/regular.*expression/p'
this is a regular pattern expression
? 可以使用這個模式輕松查找可能出現在數據流中文本行內任意位置的多個單詞。
- 星號還能用在字符組上。它允許指定可能在文本中出現多次的字符組或字符區間。
$ echo "bt" | sed -n '/b[ae]*t/p'
bt
$ echo "bat" | sed -n '/b[ae]*t/p'
bat
$ echo "bet" | sed -n '/b[ae]*t/p'
bet
$ echo "btt" | sed -n '/b[ae]*t/p'
btt
$ echo "baat" | sed -n '/b[ae]*t/p'
baat
$ echo "baaeeet" | sed -n '/b[ae]*t/p'
baaeeet
$ echo "baeeaeeat" | sed -n '/b[ae]*t/p'
baeeaeeat
$ echo "baakeeet" | sed -n '/b[ae]*t/p'
? 只要a和e字符以任何組合形式出現在 b 和 t 字符之間(就算完全不出現也行),模式就能夠匹配。如果出現了字符組之外的字符,該模式匹配就會不成立。
三、擴展正則表達式
? POSIX ERE模式包括了一些可供Linux應用和工具使用的額外符號。gawk程序能夠識別ERE模式,但sed編輯器不能。
警告:sed編輯器和gawk程序的正則表達式引擎之間是有區別的。gawk程序可以使用大多數擴展正則表達式模式符號,并且能提供一些額外過濾功能,而這些功能都是sed編輯器所不具備的。但正因為如此,gawk程序在處理數據流時通常才比較慢。
? 本節將介紹可用在gawk程序腳本中的較常見的ERE模式符號。
1. 問號
- 問號類似于星號,不過有點細微的不同。問號表明前面的字符可以出現0次或1次,但只限于此。它不會匹配多次出現的字符。
$ echo "bt" | gawk '/be?t/{print $0}'
bt
$ echo "bet" | gawk '/be?t/{print $0}'
bet
$ echo "beet" | gawk '/be?t/{print $0}'
$ echo "beeet" | gawk '/be?t/{print $0}'
- 與星號一樣,你可以將問號和字符組一起使用。
$ echo "bt" | gawk '/b[ae]?t/{print $0}'
bt
$ echo "bat" | gawk '/b[ae]?t/{print $0}'
bat
$ echo "bot" | gawk '/b[ae]?t/{print $0}'
$ echo "bet" | gawk '/b[ae]?t/{print $0}'
bet
$ echo "baet" | gawk '/b[ae]?t/{print $0}'
$ echo "beat" | gawk '/b[ae]?t/{print $0}'
$ echo "beet" | gawk '/b[ae]?t/{print $0}'
2. 加號
- 加號是類似于星號的另一個模式符號,但跟問號也有不同。加號表明前面的字符可以出現1
次或多次,但必須至少出現1次。如果該字符沒有出現,那么模式就不會匹配。
#e至少要出現一次
$echo "bt" | gawk '/be+t/{print $0}'
$ echo "bet" | gawk '/be+t/{print $0}'
beeet
$ echo "beet" | gawk '/be+t/{print $0}'
beet
#如果字符組中定義的任一字符出現了,文本就會匹配指定的模式。
$$ echo "beat" | gawk '/b[ae]+t/{print $0}'
beat
$ echo "beet" | gawk '/b[ae]+t/{print $0}'
beet
$ echo "beeat" | gawk '/b[ae]+t/{print $0}'
beeat
3.使用花括號
-
ERE中的花括號允許你為可重復的正則表達式指定一個上限。這通常稱為間隔(interval)。可以用兩種格式來指定區間。
- m :正則表達式準確出現 m 次。
- m, n :正則表達式至少出現 m 次,至多 n 次。
這個特性可以精確調整字符或字符集在模式中具體出現的次數。
警告:默認情況下,gawk程序不會識別正則表達式間隔。必須指定gawk程序的 --re- interval命令行選項才能識別正則表達式間隔。
- 這里有個使用簡單的單值間隔的例子。
$ echo "bt" | gawk --re-interval '/be{1}t/{print $0}'
$ echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
bet
$ echo "beet" | gawk --re-interval '/be{1}t/{print $0}'
? 通過指定間隔為1,限定了該字符在匹配模式的字符串中出現的次數。如果該字符出現多次或不出現,模式匹配就不成立。
- 很多時候,同時指定下限和上限也很方便。
$ echo "bt" | gawk --re-interval '/be{1,2}t/{print $0}'
$ echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'
bet
$ echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}'
beet
$ echo "beeet" | gawk --re-interval '/be{1,2}t/{print $0}'
? 字符 e 可以出現1次或2次,這樣模式就能匹配;否則,模式無法匹配。
- 間隔模式匹配同樣適用于字符組。
$ echo "bt" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$ echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bat
$ echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bet
$ echo "beat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beat
$ echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beet
$ echo "beeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beeat
$ echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
baeet
$ echo "baeaet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
baeaet
$ echo "baeaeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
?如果字母a或e在文本模式中只出現了1~2次,則正則表達式模式匹配;否則,模式匹配失敗。
4. 管道符號
-
管道符號允許你在檢查數據流時,用邏輯 OR 方式指定正則表達式引擎要用的兩個或多個模式。如果任何一個模式匹配了數據流文本,文本就通過測試。如果沒有模式匹配,則數據流文本匹配失敗。
使用管道符號的格式如下:expr1|expr2|...
這里有個例子。
$ echo "The cat is asleep" | gawk '/cat|dog/{print $0}'
The cat is asleep
$ echo "The dog is asleep" | gawk '/cat|dog/{print $0}'
The dog is asleep
$ echo "The sheep is asleep" | gawk '/cat|dog/{print $0}'
這個例子會在數據流中查找正則表達式 cat 或 dog 。正則表達式和管道符號之間不能有空格,否則它們也會被認為是正則表達式模式的一部分。
- 管道符號兩側的正則表達式可以采用任何正則表達式模式(包括字符組)來定義文本。
$ echo "He has a hat." | gawk '/[ch]at|dog/{print $0}'
He has a hat.
5. 表達式分組
- 正則表達式模式也可以用圓括號進行分組。當你將正則表達式模式分組時,該組會被視為一個標準字符。可以像對普通字符一樣給該組使用特殊字符。舉個例子:
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
? 結尾的 urday 分組以及問號,使得模式能夠匹配完整的 Saturday 或縮寫 Sat 。
- 將分組和管道符號一起使用來創建可能的模式匹配組是很常見的做法。
$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}'
cat
$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
cab
$ echo "bat" | gawk '/(c|b)a(b|t)/{print $0}'
bat
$ echo "bab" | gawk '/(c|b)a(b|t)/{print $0}'
bab
$ echo "tab" | gawk '/(c|b)a(b|t)/{print $0}'
$ echo "tac" | gawk '/(c|b)a(b|t)/{print $0}'