轉自 坐看風起
第一章 正則表達式概述
正則表達式(Regular Expression)起源於人類神經系統的研究。正則表達式的定義有以下幾種:
l用某種模式去匹配一類字符串的公式,它主要是用來描述字符串匹配的工具。
l描述了一種字符串匹配的模式。可以用來檢查字符串是否含有某種子串、將匹配的子串做替換或者從中取出符合某個條件的子串等。
l由普通字符(a-z)以及特殊字符(元字符)組成的文字模式,正則表達式作爲一個模版,將某個字符模式與所搜索的字符串進行匹配。
l用於描述某些規則的的工具。這些規則經常用於處理字符串中的查找或替換字符串。也就是說正則表達式就是記錄文本規則的代碼。
l用一個字符串來描述一個特徵,然後去驗證另一個字符串是否符合這個特徵。
以上這些定義其實也就是正則表達式的作用。
第二章 正則表達式基礎理論
這些理論將爲編寫正則表達式提供法則和規範,正則表達式主要包括以下基礎理論:
l元字符
l字符串
l字符轉義
l反義
l限定符
l替換
l分組
l反向引用
l零寬度斷言
l匹配選項
l註釋
l優先級順序
l遞歸匹配
2.1元字符
在正則表達式中,元字符(Metacharacter)是一類非常特殊的字符,它能夠匹配一個位置或字符集合中的一個字符,如:、 \w等。根據功能,元字符可以分爲兩種類型:匹配位置的元字符和匹配字符的元字符。
2.1.1匹配位置的元字符
包括:^、$、和\b。其中^(脫字符號)和$(美元符號)都匹配一個位置,分別匹配行的開始和結尾。比如,^string匹配以string開頭的行,string$匹配以string結尾的行。^string$匹配以string開始和結尾的行。單個$匹配一個空行。單個^匹配任意行。\b匹配單詞的開始和結尾,如:\bstr匹配以str開始的單詞,但\b不匹配空格、標點符號或換行符號,所以,\bstr可以匹配string、string fomat等單詞。\bstr正則表達式匹配的字符串必須以str開頭,並且str以前是單詞的分界處,但此正則表達式不能限定str之後的字符串形式。以下正則表達式匹配以ing結尾的字符串,如string、This is a string等
Ing\b
正則表達式ing\b匹配的字符串必須以ing結尾,並且ing後是分界符,以下正則表達式匹配一個完整的單詞:\bstring\b。
2.1.2 匹配字符的元字符
匹配字符的元字符有7個:.(點號)、\w、\W、、s\、\S、\d和\D。其中點號匹配除換行之外的任意字符;\w匹配單詞字符(包括字母、漢字、下劃線和數字);\W匹配任意非單詞字符、\s匹配任意的空白字符,如空格、製表符、換行等;\S匹配任意的非空白字符;\d匹配任意數字字符;\D匹配任意的非數字字符。如:
^.$匹配一個非空行,在該行中可以包含除了換行符以外的任意字符。
^\w$匹配一個非空行,並且該行中只能包含字母、數字、下劃線和漢字中的任意字符。
\ba\w\w\w\w\w\w\\b匹配以字母a開頭長度等於7的任意單詞
\ba\w\w\w\d\d\d\D\b匹配以字母a開頭後面有3個字符三個數字和1個非數字字符長度等於8的單詞
2.2 字符類
字符類是一個字符集合,如果該字符集合中的任何一個字符被匹配,則它會找到該匹配項。字符類可以在[](方括號)中定義。如:
[012345]可以匹配數字0到5中的任意一個。
<H[123456]>可以匹配HTML標籤中的H1到H6。
[Jj]ack可以匹配字符串Jack或jack。
但是,由於表達式[0123456789]書寫非常不方便,連字符(-)便應用而生,[0-9]等價於[0123456789]。[a-z]匹配任何小寫字母,[A-Z]匹配任意大寫字母。如果要在字符類中包含連字符,則必須包含在第一位,如:[-a]表示表達式匹配-或者a。在字符類中如果^是字符類的第一個字符表示否定該字符串,也就是匹配該字符串外的任意字符,如:[^abc]匹配除了abc以外的任意字符,[^-]匹配除了連字符以外的任意字符,a[^b]匹配a之後不是b的字符串。
表2-1常用的字符類
字符或表達式 |
說明 |
\w |
匹配單詞字符(包括字母、數字、下劃線和漢字) |
\W |
匹配任意的非單詞字符(包括字母、數字、下劃線和漢字) |
\s |
匹配任意的空白字符,如空格、製表符、換行符、中文全角空格等 |
\S |
匹配任意的非空白字符 |
\d |
匹配任意數字 |
\D |
匹配任意的非數字字符 |
[abc] |
匹配字符集中的任何字符 |
[^abc] |
匹配除了字符集中包含字符的任意字符 |
[0-9a-z_A-Z_] |
匹配任何數字、字母、下劃線。等同於\w |
\p{name} |
匹配{name}指定的命名字符類中的任何字符 |
\P{name} |
匹配除了{name}指定的命名字符類中之外的任何字符 |
. |
匹配除了換行符號之外的任意字符 |
[^0-9a-zA-Z_] |
等同於\W |
2.3字符轉義
表2-2:常用的轉義字符
表達式 |
可匹配 |
\r, \n |
代表回車和換行符 |
\t |
製表符 |
\\ |
代表 "\" 本身 |
還有其他一些在後邊章節中有特殊用處的標點符號,在前面加 "\" 後,就代表該符號本身。比如:^, $ 都有特殊意義,如果要想匹配字符串中 "^" 和 "$" 字符,則表達式就需要寫成 "\^" 和 "\$"。
表達式 |
可匹配 |
\^ |
匹配 ^ 符號本身 |
\$ |
匹配 $ 符號本身 |
\. |
匹配小數點(.)本身 |
2.4 反義
在使用正則表達式時,如果需要匹配不在字符類指定的範圍內的字符時,可以使用反義規則。其實我們已經使用過反義表達式,如\W、\S、\D、[^abc]等。常用的反義表如下:
表2-3:常用的反義表達式
字符或表達式 |
說明 |
\W |
匹配任意不是字母,數字,下劃線,漢字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非數字的字符 |
\B |
匹配不是單詞開頭或結束的位置 |
[^x] |
匹配除了x以外的任意字符 |
[^aeiou] |
匹配除了aeiou這幾個字母以外的任意字符 |
2.4 限定符
正則表達式的元字符一次只能匹配一個位置或一個字符,如果需要匹配零個一個或多個字符時,則需要使用限定符。限定符用於指定允許特定字符或字符集自身重複出現的次數。如{n}表示出現n次;{n,}表示重複至少n次;{n,m}表示至少出現n次最
多m次。常用限定符如下表:
表2-4:常用限定符
字符 |
描述 |
* |
匹配前面的子表達式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等價於{0,}。 |
+ |
匹配前面的子表達式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。 |
? |
匹配前面的子表達式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等價於 {0,1}。 |
{n} |
n 是一個非負整數。匹配確定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。 |
{n,} |
n 是一個非負整數。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價於 'o+'。'o{0,}' 則等價於 'o*'。 |
{n,m} |
m 和 n 均爲非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。劉, "o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價於 'o?'。請注意在逗號和兩個數之間不能有空格。 |
*? |
儘可能少的使用重複的第一個匹配 |
+? |
儘可能少的使用重複但至少使用一次 |
?? |
如果有可能使用零次重複或一次重複。 |
{n}? |
等同於{n} |
{n,}? |
儘可能少的使用重複但至少重複n次 |
{n,m}? |
介於n次和m次之間,儘可能少的使用重複。 |
2.5 貪婪、惰性和支配性匹配
惰性匹配:先看字符串中的第一個字母是不是一個匹配,如果單獨一個字符還不夠就讀入下一個字符,如果還沒有發現匹配,就不斷地從後續字符中讀取,只道發現一個合適的匹配,然後開始下一次的匹配。
貪婪匹配:先看整個字符串是不是一個匹配,如果沒有發現匹配,它去掉字符串中最後一個字符並再次嘗試,如果還沒有發現匹配,那麼再次去掉最後一個字符,這個過程會一直重複直到發現一個匹配或字符串不剩一個字符爲止。
支配性匹配:只嘗試匹配整個字符串,如果整個字符串不能產生一個匹配,則不再進行嘗試。
我們普通的字符類均是貪婪匹配,如果在字符類後加個問號(?)則表示懶惰匹配,要成爲支配性匹配則在懶惰匹配後加個問號(?).
2.6替換
正則表達式0\d{2}-\d{8}和0\d{3}-\d{7}分別匹配區號爲3位和4位的固定電話號碼,如果需要同時匹配區號爲3位和4位的固定電話號碼,可以使用替換滿足這一需求。最簡單的替換是使用豎線(|)表示。以下表達式匹配了區號爲3位號碼爲8位和區號爲4位號碼爲7位的的電話號碼,區號和號碼均使用-連接,0\d{2}-\d{8}|0\d{3}-\d{7}
表2-5常用替換
字符或表達式 |
說明 |
| |
匹配豎線(|)左側或右側 |
(?(表達式)yes|no) |
表達式要麼與yes部分匹配要麼與no部分匹配,其中no部分可以省略。 |
(?(name)yes|no) |
以name命名的字符串要麼與yas部分匹配,要麼與no部分pp,其中no部分可以省略 |
2.7 分組
分組又稱爲子表達式,即把一個正則表達式的全部或部分分成一個或多個組。其中分組使用圓括號(),分組後把圓括號中的表達式看做一個整體來處理,比如:(abc){1,2}表示abc出現一次或兩次的字符串,其中把abc看做一個整體來進行匹配。
2.8 後向引用
當一個正則表達式被分組後,每一個組將自動被賦予一個組號,該組號可以代表該組的表達式。其中,組號的編制規則爲:從左到右,以分組的左括號爲標誌,第一個組號爲1,第二個分組號爲2,以此類推。如:(A?(B?(C?)))將產生3個組號,第一組爲:(A?(B?(C?)));第二組爲: (B?(C?));第三組爲:(C?)。
反向引用提供了查找重複字符組的方便方法,反向引用可以使用數字命名(默認名稱)的組號,也可以使用指定命名的組號。比如:\b(\w)\1\b匹配兩個字符一樣的單詞,此表達式和\b(\w)\w\b不一樣,後者兩個字符可以不一樣。再看,\b(\w)(\d)\1\2\b,匹配一個字符和一個數字然後重複字符和數字。\b\w*(\w+)\1\b匹配以至少兩個字符一樣結尾的單詞。\b(\w+)\b\s+\1\b此正則表達式匹配的具體過程如下:
a. 表達式\b(\w+)\b匹配一個單詞並且單詞的長度至少爲1
b. 表達式\s+匹配一個或多個空白字符
c. 表達式\1將重複子表達式(\w+)匹配的內容,及匹配重複的單詞
d. 匹配單詞的結束位置。
分組不僅可以使用數字作爲組號,還可以使用自定義名稱作爲組號。以下兩個正則表達式都是將分組後的子表達式\w+命名爲word.
(?<word>\w+)
(?’word’\w+)
因此\b(\w+)\b\s+\1\b和以下正則表達式是等價的,都匹配重複的單詞:
\b(?<word>\w+)\b\s+\k<word>\b
表2-5後向引用說明表
表達式 |
說明 |
\數字 |
使用數字命名的後向引用 |
\k<name> |
使用指定命名的後向引用 |
表2-6常用分組說明
字符 |
說明 |
(expression) |
匹配字符串expression,並將匹配的文本保存到自動命名的組裏 |
(?<nane> expression) |
匹配字符串expression,並將匹配的文本保存到以name命名的變量中,該名稱不能包含標點符號,不能以數字開頭。 |
(?:expression) |
匹配字符串expression,不保存匹配的文本,也不分配組號 |
(?!expression) |
匹配後面不是字符串expression的位置 |
(?=expression) |
匹配字符串expression前面的位置 |
(?<=expression) |
匹配字符串expression後面的位置 |
(?<!expression) |
匹配前面不是字符串expression的位置 |
(?>expression) |
只匹配expression一次 |
2.9 零寬度斷言
元字符^、\b、$都匹配一個位置,並且這個位置滿足一定條件。在此把滿足一個條件稱爲斷言或零寬度斷言。正則表達式中零寬度斷言說明如下表:
表2-6零寬度斷言
字符(斷言) |
說明 |
^ |
匹配行的開始位置 |
$ |
匹配行的結束位置 |
\A |
匹配必須出現在字符串的開頭 |
\Z |
匹配必須出現在字符串的結尾或字符串結尾處的換行符(\n)前 |
\z |
匹配必須出現在字符串的結尾 |
\G |
匹配必須出現在上個匹配結束的地方 |
\b |
匹配單詞的開始或結束的位置 |
\B |
匹配不是單詞的開始或結束的位置 |
表達式(?=expression)、(?!expression)、(?<=expression)、和(?<!expression)都是匹配一個位置。下面將詳細介紹表達式(?=expression)和(?<=expression)。
(?=expression)又稱爲零寬度正預測先行斷言,它斷言自身位置的前面能夠匹配表達式expression。以下正則表達式匹配以ed結尾的單詞的前面部分:\b\w+(?=ed\b)。
(?<=expression)又稱爲零寬度正回顧後發斷言,它斷言自身位置的後面能夠匹配表達式expression,以下正則表達式匹配以an開頭的單詞的後面部分,即匹配單詞除了字符串an之外的部分:(?<=\ban)\w+\b
2.10 負向零寬度斷言
零寬度斷言只能指定或匹配一個位置,而負向零寬度斷言與零寬度斷言正好相反,它能指定或匹配不是一個位置,即所說的反義。特別是在匹配字符串中不包含指定的字符時,負向零寬度斷言特別有用,比如要匹配斷言字符a之後不能是字符b的表達式爲:
\b\w*a(?!b)\w*\b
因此該表達式匹配一個單詞,並且這個包含字符a並且a後面不是緊隨着b
表達式(?!expression)稱爲負向零寬度斷言,它斷言自身位置後不能包含expression。以下正則表達式匹配一個z字符串,字符串前三位爲字符並且後邊不是緊隨着數字:\b\w{3} (?!\d+);表達式(?<!expression)稱爲零寬度回顧後發斷言,它斷言自身位置的前面不能匹配字符串expression。以下表達式匹配不以數字開頭、並且字符串中只包含大寫字母、小寫字母或下劃線。
(?<!\d+) [a-z-A-Z]+
2.11匹配選項
匹配選項可以指定正則表達式匹配中的行爲,如忽略大小寫、處理多行、處理單行、從右到左開始匹配等。常用的匹配選項如下:
表2-7常用匹配選項
RegexOptions枚舉值 |
內聯標誌 |
簡單說明 |
ExplicitCapture |
n |
只有定義了命名或編號的組才捕獲 |
IgnoreCase |
i |
不區分大小寫 |
IgnorePatternWhitespace |
x |
消除模式中的非轉義空白並啓用由#標記的註釋。 |
MultiLine |
m |
多行模式,其原理是修改了^和$的含義 |
SingleLine |
s |
單行模式,和MultiLine相對應 |
2.12 優先級
正則表達式從左到右進行計算,並遵循優先級順序,這與算術表達式非常類似。下表從最高到最低說明了各種正則表達式運算符的優先級順序:
表2-8:優先級說明
運算符 |
說明 |
\ |
轉義符 |
(), (?:), (?=), [] |
括號和中括號 |
*, +, ?, {n}, {n,}, {n,m} |
限定符 |
^, $, \anymetacharacter, anycharacter |
定位點和序列 |
| |
替換 |
字符的優先級比替換運算符高,替換運算符允許“m|food”與“m”或“food”匹配。若要匹配“mood”或“food”,請使用括號創建子表達式,從而產生“(m|f)ood”。
2.12 遞歸匹配
遞歸匹配在匹配具有嵌套結構的字符串時特別有效。比如算術表達式((1+2)*(3+4))具有嵌套結構,如果要使用正則表達式檢查該表達式是否正確,則可以使用遞歸匹配解決該問題。
這裏介紹的平衡組語法是由.Net Framework支持的;其它語言/庫不一定支持這種功能,或者支持此功能但需要使用不同的語法。
有時我們需要匹配像( 100 * ( 50 + 15 ) )這樣的可嵌套的層次性結構,這時簡單地使用\(.+\)則只會匹配到最左邊的左括號和最右邊的右括號之間的內容(這裏我們討論的是貪婪模式,懶惰模式也有下面的問題)。假如原來的字符串裏的左括號和右括號出現的次數不相等,比如( 5 / ( 3 + 2 ) ) ),那我們的匹配結果裏兩者的個數也不會相等。有沒有辦法在這樣的字符串裏匹配到最長的,配對的括號之間的內容呢?
爲了避免(和\(把你的大腦徹底搞糊塗,我們還是用尖括號代替圓括號吧。現在我們的問題變成了如何把xx <aa <bbb> <bbb> aa> yy這樣的字符串裏,最長的配對的尖括號內的內容捕獲出來?
這裏需要用到以下的語法構造:
- (?'group')把捕獲的內容命名爲group,並壓入堆棧(Stack)
- (?'-group')從堆棧上彈出最後壓入堆棧的名爲group的捕獲內容,如果堆棧本來爲空,則本分組的匹配失敗
- (?(group)yes|no)如果堆棧上存在以名爲group的捕獲內容的話,繼續匹配yes部分的表達式,否則繼續匹配no部分
- (?!)零寬負向先行斷言,由於沒有後綴表達式,試圖匹配總是失敗
如果你不是一個程序員(或者你自稱程序員但是不知道堆棧是什麼東西),你就這樣理解上面的三種語法吧:第一個就是在黑板上寫一個"group",第二個就是從黑板上擦掉一個"group",第三個就是看黑板上寫的還有沒有"group",如果有就繼續匹配yes部分,否則就匹配no部分。
我們需要做的是每碰到了左括號,就在壓入一個"Open",每碰到一個右括號,就彈出一個,到了最後就看看堆棧是否爲空--如果不爲空那就證明左括號比右括號多,那匹配就應該失敗。正則表達式引擎會進行回溯(放棄最前面或最後面的一些字符),儘量使整個表達式得到匹配。
< #最外層的左括號
[^<>]* #最外層的左括號後面的不是括號的內容
(
(
(?'Open'<) #碰到了左括號,在黑板上寫一個"Open"
[^<>]* #匹配左括號後面的不是括號的內容
)+
(
(?'-Open'>) #碰到了右括號,擦掉一個"Open"
[^<>]* #匹配右括號後面不是括號的內容
)+
)*
(?(Open)(?!)) #在遇到最外層的右括號前面,判斷黑板上還有沒有沒擦掉的"Open";如果還有,則匹配失敗
> #最外層的右括號
平衡組的一個最常見的應用就是匹配HTML,下面這個例子可以匹配嵌套的<div>標籤:<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.