Linux
中大部分時間都是在處理文本內容,而為了更加快速和自動化的處理文本,我們就需要正則表達式。正則表達式提供了功能強大、靈活而又高效的方式來處理文本。正則表達式的模式匹配可以使你在大量的文本內容中快速找到特定的字符序列。驗證文本來確保所匹配的模式符合某種規則:如電子郵件地址,還可以提起,編輯,刪除文本字符串。對于快速處理大量的文本內容,正則表達式是不可或缺的工具。
正則表達式是你所定義的模式模版(pattern template
),Linux
工具可以使用它來過濾和處理文本。如grep
,sed,
awk`等工具都支持正則表達式。如果數據匹配模式,它就會被接受并進行下一步處理,如果數據不匹配模式則它會被丟棄。下圖描述下這個過程:
文本數據 -----> 正則表達式 -------> 匹配的數據
|
|
|
↓
丟棄的數據
1. 正則表達式的類型
正則表達式是通過正則表達式引擎(regular expression engine)實現的。正則表達式引擎是一套底層軟件,負責解釋正則表達式模式并使用這些模式進行文本匹配。
在Linux
中,有兩種流行的正則表達式引擎:
- BRE: POSIX基礎正則表達式引擎(basic regular expression)
- ERE: POSIX擴展正則表達式引擎(extended regular expression)
2. BRE模式
最基本的BRE
模式就是匹配數據流中的文本字符。
2.1 純文本
下面使用sed
程序使用正則表達式過濾數據.
$ echo "This is a test" | sed -n '/test/p'
This is a test
$ echo "This is a test" | sed -n '/tset/p'
$
$ echo "This is a test" | sed -n '/this/p'
$
$ echo "This is a test" | sed -n '/This/p'
This is a test
$
第一個例子使用test
來匹配數據流中的文本。由于echo
語句中包含了單詞test
,所以sed
能匹配到該文本模式,然后使用p
命令輸出匹配到的文本行。第二個例子使用tset
,然后文本流中并沒有這個單詞,所以sed
并沒有匹配到這個模式,就沒有輸出任何數據。第三個例子使用的this
,這次也沒有匹配成功,應該文本流中并沒有小寫的this
,所以也沒有匹配成功。第四次文本流中出現了This
,所以匹配成功。
正則表達式并不關心模式匹配出現在數據流的位置,它也不關心模式出現了多少次,一旦匹配到該模式,則會把數據流中的文本行傳回給Linux
命令。但是正則表達式會區分大小寫,它們只匹配字符序列和模式一樣的文本行。匹配到的文本行中必須出現模式,而且文本流中匹配到的字符序列必須和模式中的字符順序一樣。
$ echo "There are many dog." | sed -n '/dogs/p'
$
$ echo "There are many dogs." | sed -n '/dogs/p'
There are many dogs.
$
上面的第一個例子匹配dogs
,但是文本流中只有dog
,所以匹配失敗。第二個例子則成功的匹配到了dogs
。所以模式中的字符串必須安裝順序全部出現在文本流中才能匹配成功。
如果只使用上面這張字符串匹配則并不能一次處理大量的文本。如果想一次匹配很多字符串則必須把它們都寫出來,這并不能發揮正則表達式的強大能力,下面會介紹正則表達式中非常有用的一些特殊字符來構造復雜的模式。
2.2 特殊字符
有些字符在正則表達式中有特別的含義,結合這些特殊字符可以構造復雜的模式匹配。這些特殊字符包括:
.*[]^$\+?|()
因為這些特殊字符在正則表達式中具有特殊意義,如果只是想把它們作為文本字符來匹配則需要使用\
來轉義。而且如果想匹配反斜線也必須使用\
來轉義。
$ echo "\ is a special character" | sed -n '///p'
$
$ echo "\ is a special character" | sed -n '/\//p'
\ is a special character
$
2.3 錨字符
默認情況下,當匹配模式時,只要模式在數據流的任何位置出現,模式匹配都會成功。有兩個字符可以現在模式在數據流中出現的位置。
2.3.1 行首
^
字符定義從數據流中文本行的行首開始的模式。如果模式出現在行首則匹配成功,否則無法匹配。要使用^
,必須將它放到正則表達式中模式的前面。
$ echo "Make American great again." | sed -n '/^make/p'
$
$ echo "Make American great again." | sed -n '/^Make/p'
Make American great again.
2.3.2 行尾
和^
一樣,$
表示模式必須出現在行尾。將這個特殊字符放在文本模式后面來指明數據必須以該文本模式結尾。
$ echo "MAKE AMERICAN GREAT AGAIN." | sed -n '/again$/p'
$
$ echo "MAKE AMERICAN GREAT AGAIN." | sed -n '/AGAIN$/p'
MAKE AMERICAN GREAT AGAIN.
2.3.2 組合錨點
在某些情況下可以在同一行中把行首錨點和行尾錨點組合起來使用。一種情況是為了查找只含有特定文本的數據行。
$ cat data1.txt
make american great again.
Make american great again.
MAKE AMERICAN GREAT AGAIN.
$ sed -n '/^MAKE AMERICAN GREAT AGAIN.$/p' data1.txt
MAKE AMERICAN GREAT AGAIN.
$
還有一個情況就是在行首錨點和行尾錨點之間不加任何文本,這樣就會過濾出數據流中的空白行。
$ cat data2.txt
make american great again.
Make american great again.
MAKE AMERICAN GREAT AGAIN.
$ sed -n '/^$/d' data2.txt
make american great again.
Make american great again.
MAKE AMERICAN GREAT AGAIN.
$
上面定義的正則表達式模式會查找行首和行尾之間什么都沒有的數據行。然后使用d
命令來刪除匹配到的空白行,因此輸出的文本流中就沒有空白行了。這是從文檔中刪除空白行的很有效的方法。
2.4 點字符
特殊字符點號用來匹配除換行符之外的任意一個字符。在點號出現的位置必須有且只有一個字符,才能匹配成功
$ cat zip.txt
bzip2
bunzip
gzip
gunzip
unzip
zip
$ sed -n '/.zip/p' zip.txt
bzip2
bunzip
gzip
gunzip
unzip
$
上面的例子中.zip
表示最少得匹配4個字符,其中第一個可以是任意字符,后面三個必須是zip
。所以最后一行沒有匹配成功,其他則都匹配成功。
2.5 字符組
特殊字符點號在匹配某個位置上的任意字符時很有效,但是如果想限制字符的范圍的時候就沒有那么給力了。但是正則表達式中的字符組(character class)
可以用來限定待匹配的字符是一組字符中任意一個。如果字符組中的某個字符出現在數據流中則匹配成功。
使用方括號來定義一個字符組。方括號中的字符是你希望匹配的字符,然后你就可以使用整個組來匹配字符。
$ sed -n '[bg]zip' zip.txt
bzip2
gzip
$
上面的模式匹配中的方括號就是字符組,只要匹配到其中的任何一個字符都會匹配成功。這個匹配模式可以匹配bzip
和gzip
。字符組中的位置必須要有其中的一個字符出現才能匹配成功。字符組中不僅能使用字符,也可以使用數字。
$ cat data3.txt
This is test line0.
This is test line1.
This is test line2.
This is test line3.
This is a end line.
$ sed -n '/[12345]/p' data3.txt
This is test line1.
This is test line2.
This is test line3.
$
這個正則表達式模式匹配數據行中有數字0
,1
,2
,3
,4
,5
的行。其他數字以及沒有數字的行都會被過濾。
可以講字符組組合在一起以檢查特定的字符序列是否符合某種格式:比如電話號碼和郵編。
cat data4.txt
606333
462018
2230011
4353
51900
$ sed -n '
> /[0123456789][0123456789][0123456789][0123456789][0123456789][0123456789]/
> ' data4.txt
606333
462018
2230011
上面的例子成功的過濾了6
位數以下的郵編,但是它也讓7
位數的郵編通過了模式匹配。正則表達式只有在文本流中匹配到了最小的模式字符串就匹配成功,上面的例子中7
位數字中的前6
位已經匹配成功,則整行文本都會匹配成功。如果想只匹配長度為6個數字的郵編則可以使用行首行尾限定符:
$ sed -n '
> /^[0123456789][0123456789][0123456789][0123456789][0123456789][0123456789]$/
> ' data4.txt
606333
462018
$
2.6 區間
上面的例子只是匹配6個數字的郵編,如果我們想匹配11位的移動手機號碼,如果按照上面的方式...:( 。好在有一種可以方式可以避免寫那么多字母和數字。這就是區間,只要在方括號的開始和結束指定一個字符,中間使用破折號分隔這兩個字符就可以了。現在可以優化下上面的郵編的例子:
$ sed -n '
> /^[0-9][0-9][0-9][0-9][0-9][0-9]$/
> ' data4.txt
606333
462018
$
這個模式和上面的是一樣的,只能匹配6位數字。同樣的方法也使用于字母字符:
$ sed -n '/[c-h]at/p' data5.txt
The cat is sleeping.
That is a very nice hat.
$
上面的例子中[c-h]
匹配c
和h
之間的所有字母。還可以在單個字符組中指定多個字符區間:
$ sed -n '/[c-hx-z]at/p' data5.txt
The cat is sleeping.
That is a very nice hat.
$
2.7 排除型字符組
在正則表達式中我們也可以反轉字符組的作用。就是匹配不在字符組中的字符。在字符組的前面加上^
,就可以匹配字符組之外的字符了。
$ sed -n '/[^c-h]at/p' data5.txt
$
通過排除型字符組,可以匹配除了c
和h
之間的所有字符。
2.8 特殊的字符組
除了自己定義的字符組之外,BRE
還包含了一些特殊的字符組,可以用來匹配特定類型的字符。
組 | 描述 |
---|---|
[[:alpha:]] | 匹配任意字母字符,不管是大寫還是小寫 |
[[:alnum:]] | 匹配任意字母數字字符09、AZ或a~z |
[[:blank:]] | 匹配空格或制表符 |
[[:digit:]] | 匹配0~9之間的數字 |
[[:lower:]] | 匹配小寫字母字符a~z |
[[:print:]] | 匹配任意可打印字符 |
[[:punct:]] | 匹配標點符號 |
[[:space:]] | 匹配任意空白字符:空格、制表符、NL、FF、VT和CR |
[[:upper:]] | 匹配任意大寫字母字符A~Z |
可以在正則表達式中將特殊表達式當作普通字符組使用:
$ 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'
$
2.9 星號
在字符后面放置星號表面前面的字符必須在模式匹配中出現0次或多次。
$ echo "k" | sed -n '/o*k/p'
k
$ echo "ok" | sed -n '/o*k/p'
ok
$ echo "oooook" | sed -n '/o*k/p'
oooook
$
o*
表示模式中可以有0個o
,1個o
,或者多個o
。
3. 擴展正則表達式
POSIX ERE
模式包括了BRE
而且還提供了一些額外的特殊符號。下面來介紹ERE
模式中剩下的特殊符號。
3.1 問號
問號類似于星號,但是問號和星號又不同。問號表示前面的字符只能出現0
次或1
次,不能是其他次數。就是說問號前面的字符要么不出現,要么只出現一次,其他情況都會匹配失敗。
$ echo "ct" | gawk '/ca?t/{print $0}'
ct
$ echo "cat" | gawk '/ca?t/{print $0}'
cat
$ echo "caat" | gawk '/ca?t/{print $0}'
$
$ echo "caaat" | gawk '/ca?t/{print $0}'
$
?
表示前面的字符只能出現0
次或1
次,其他情況都不會匹配成功。?
也可以和字符組一起使用:
$ echo "ct" | gawk '/c[au]?t/{print $0}'
ct
$ echo "cat" | gawk '/c[au]?t/{print $0}'
cat
$ echo "cut" | gawk '/c[au]?t/{print $0}'
cut
$ echo "cot" | gawk '/c[au]?t/{print $0}'
$
$ echo "caut" | gawk '/c[au]?t/{print $0}'
$
$ echo "caet" | gawk '/c[au]?t/{print $0}'
$
3.2 加號
+
和*
、?
類似,加號表示前面的字符最少出現1
次。如果加號前面的字符沒有出現則匹配失敗。
$ echo "ct" | gawk '/ca+t/{print $0}'
$
$ echo "cat" | gawk '/ca+t/{print $0}'
cat
$ echo "cut" | gawk '/ca+t/{print $0}'
$
$ echo "caat" | gawk '/ca+t/{print $0}'
caat
$ echo "caet" | gawk '/ca+t/{print $0}'
$
$ echo "caaat" | gawk '/ca+t/{print $0}'
caaat
$
如果a
字符沒有出現則匹配失敗。+
也可以作用于字符組:
$ echo "ct" | gawk '/c[au]+t/{print $0}'
$
$ echo "cat" | gawk '/c[au]+t/{print $0}'
cat
$ echo "cut" | gawk '/c[au]+t/{print $0}'
cut
$ echo "caat" | gawk '/c[au]+t/{print $0}'
caat
$ echo "caut" | gawk '/c[au]+t/{print $0}'
caut
$ echo "caaaaut" | gawk '/c[au]+t/{print $0}'
caaaaut
$
3.3 花括號
ERE
中的花括號允許前面的模式可以重復出現的次數。這通過稱為間隔(interval
)。可以用四種方式來指定:
限定符 | 描述 |
---|---|
{n} | 匹配前面的元素,如果它確切地出現了 n 次。 |
{n,m} | 匹配前面的元素,如果它至少出現了 n 次,但是不多于 m 次。 |
{n,} | 匹配前面的元素,如果它出現了 n 次或多于 n 次。 |
{,m} | 匹配前面的元素,如果它出現的次數不多于 m 次。 |
$ echo "ct" | grep -E 'ca{1}t'
$
$ echo "cat" | grep -E 'ca{1}t'
cat
$ echo "caat" | grep -E 'ca{1}t'
$
設置間隔為1
則表示a
必須且只能出現一次,其他情況都不會匹配成功。grep -E
可以識別ERE
模式下的正則表達式。也可以單獨設置重復出現的最少次數和最大次數,也可以設置一起設置最小和最大次數。
3.4 Alternation(交替)
管道符號允許你在檢查正則表達式時,用邏輯OR
方式指定多個模式。如果匹配了其中任何一個單獨的模式則整個模式匹配成功,如果沒有模式匹配上,則數據流匹配失敗。使用管道符號的格式如下:
expr1 | expr2 | ...
$ echo "There is a dog." | gawk '/cat|dog/{print $0}'
There is a dog.
$ echo "There is a cat." | gawk '/cat|dog/{print $0}'
There is a cat.
$ echo "He has a cat" | gawk '/[ch]at|dog/{print $0}'
He has a cat
$
管道符號兩側的正則表達式可以采用任何形式的正則表達式來定義文本(包括字符組)。
3.5 表達式分組
可以使用圓括號對正則表達式分組,當你將正則表達式分組時,該組會被視為一個標準字符。可以像普通字符一樣給改組使用特殊字符。
$ echo "Sat" | gawk '/Sat(urday)?/{print $0}'
Sat
$ echo "Saturday" | gawk '/Sat(urday)?/{print $0}'
Saturday
$
結尾的urday
分組就像一個普通字符,后面的問號表示urday
可以不出現或者只出現一次。將分組和管道一起使用是很常見的做法。
$ 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}'
$
上面就是 POSIX
的正則表達式。每種編程語言都會在 POSIX
基礎上提供更加豐富的符號集。上面只是最基本的正則表達式,其他更加復雜的就需要你們自己去學習了。加油 :)