正則表達式和字符串處理

轉自 坐看風起

第一章        正則表達式概述

正則表達式(Regular Expression)起源於人類神經系統的研究。正則表達式的定義有以下幾種:

l用某種模式去匹配一類字符串的公式,它主要是用來描述字符串匹配的工具。

l描述了一種字符串匹配的模式。可以用來檢查字符串是否含有某種子串、將匹配的子串做替換或者從中取出符合某個條件的子串等。

l由普通字符(a-z)以及特殊字符(元字符)組成的文字模式,正則表達式作爲一個模版,將某個字符模式與所搜索的字符串進行匹配。

l用於描述某些規則的的工具。這些規則經常用於處理字符串中的查找或替換字符串。也就是說正則表達式就是記錄文本規則的代碼。

l用一個字符串來描述一個特徵,然後去驗證另一個字符串是否符合這個特徵。

以上這些定義其實也就是正則表達式的作用。

第二章 正則表達式基礎理論

這些理論將爲編寫正則表達式提供法則和規範,正則表達式主要包括以下基礎理論:

l元字符

l字符串

l字符轉義

l反義

l限定符

l替換

l分組

l反向引用

l零寬度斷言

l匹配選項

l註釋

l優先級順序

l遞歸匹配

21元字符

在正則表達式中,元字符(Metacharacter)是一類非常特殊的字符,它能夠匹配一個位置或字符集合中的一個字符,如:、 \w等。根據功能,元字符可以分爲兩種類型:匹配位置的元字符和匹配字符的元字符。

211匹配位置的元字符

包括:^$、和\b。其中^(脫字符號)和$(美元符號)都匹配一個位置,分別匹配行的開始和結尾。比如,^string匹配以string開頭的行,string$匹配以string結尾的行。^string$匹配以string開始和結尾的行。單個$匹配一個空行。單個^匹配任意行。\b匹配單詞的開始和結尾,如:\bstr匹配以str開始的單詞,但\b不匹配空格、標點符號或換行符號,所以,\bstr可以匹配stringstring fomat等單詞。\bstr正則表達式匹配的字符串必須以str開頭,並且str以前是單詞的分界處,但此正則表達式不能限定str之後的字符串形式。以下正則表達式匹配以ing結尾的字符串,如stringThis 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]可以匹配數字05中的任意一個。

<H[123456]>可以匹配HTML標籤中的H1H6

[Jj]ack可以匹配字符串Jackjack

但是,由於表達式[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部分可以省略。

(?(nameyes|no

name命名的字符串要麼與yas部分匹配,要麼與no部分pp,其中no部分可以省略

2.7 分組

分組又稱爲子表達式,即把一個正則表達式的全部或部分分成一個或多個組。其中分組使用圓括號(),分組後把圓括號中的表達式看做一個整體來處理,比如:abc{12}表示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>.

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