正則表達式和字符串處理


正則表達式和字符串處理

來源 https://www.cnblogs.com/helloczh/articles/1648029.html

第一章        正則表達式概述

正則表達式(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>.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM