行的開(kāi)始:^
^abc 以a開(kāi)頭,接下來(lái)是b 再接下來(lái)是c的文本 (只有用在表達(dá)式的開(kāi)頭時(shí)才表示行的開(kāi)始)
行的結(jié)束:$
abc$ 以c結(jié)尾前面是b 再前面是a的文本
字符組:[]
[abc] 表示 a 或 b 或 c
連接字符: -
用于字符組內(nèi),表示一個(gè)范圍 [1-5] 等同于 [12345]
^ 用在組內(nèi)第一個(gè)字符時(shí)表示非
^[^1-6] 表示不以1-6的數(shù)字開(kāi)頭的文本
任意符:.
. 匹配任意字符
小括號(hào):()
界定范圍,其實(shí)還有個(gè)重要的作用,捕捉文本分組
多選分支:|
a(b|d)c 匹配abc或adc
多選分支和字符組都有或的意思,
但是字符組只是匹配字符組中包含字符的其中之一,
但是多選分支可能本身就是個(gè)正則表達(dá)式 比如a(a[bd]c|1[0-5]3)b
注意事項(xiàng) ^abc|123: 和 ^(abc|123): 是不同的,
前面表示匹配 abc或123:開(kāi)始的文本
后面表示匹配 abc:或123:開(kāi)始的文本 因此為了明確語(yǔ)境需要配合()來(lái)完成
單詞分界符:\< 和 \> (只有在少部分支持的系統(tǒng)中才能使用)
\<ab 以ab開(kāi)頭的單詞 比如able
和行開(kāi)始符表示的意思不同 ^ob.*表示的是以ob開(kāi)始的文本,
比如'obc my good'
\<ob.* 匹配的是以ob開(kāi)頭的單詞開(kāi)頭的文本,
因?yàn)閛bc不是個(gè)單詞,所以不能匹配
如果換成 'object my good' 就可以匹配,因?yàn)閛bject是英文單詞
問(wèn)號(hào):?
goo?d ?表示可選元素不作為匹配的必然條件 示例可匹配god,good 配合括號(hào)可作出更多的操作 go(od)?則可以匹配go,good
除了?外還有兩個(gè)量詞 + 和 * ,和一個(gè)組合量詞{}
?表示可選,即匹配一次或0次
+ 表示至少一次
* 表示任意次 0次或多次
{} 表示范圍
abc? 匹配ab abc
abc+ 匹配abcc abccccc...
abc* 匹配ab abccccc...
捕獲命名 (?P<name>...)
Python支持為捕獲的內(nèi)容進(jìn)行重命名
(?P<myName>good) 表示捕獲 good,可以通過(guò)obj.group(1) 來(lái)獲取good 但是也可以通過(guò)自定義名稱(chēng) obj.group("myName") 來(lái)獲取good
大括號(hào) {}
大括號(hào)代表區(qū)間個(gè)數(shù) [a-z]{8} 表示 連續(xù)的八個(gè)字母 [a-z]{3,8} 連續(xù)的 3 到 8 個(gè)字母
反向引用
()有捕捉文本的作用,利用這個(gè)特性可做一些反向引用的操作,比如查找重復(fù)單詞即連續(xù)出現(xiàn)兩次相同的單詞 my name is is xie,這里is是重復(fù)的如果用正則表達(dá)式找出所有的這種重復(fù)單詞要怎么做,首先要匹配的是單詞,所以要用到單詞分界符 \<is\s+is\> 這樣只可以匹配 is is 為了更通用 \<([a-zA-Z]+)\s+\1\> 這里有兩個(gè)關(guān)鍵點(diǎn)1.小括號(hào),2.\1 因?yàn)槔ㄌ?hào)內(nèi)的內(nèi)容是會(huì)被捕獲的,所以我們可以取出其捕獲的內(nèi)容 取出方法便是用\加一個(gè)數(shù)字 \1 表示取出第一個(gè)括號(hào)捕獲的內(nèi)容 因此該表達(dá)式表示 先匹配一個(gè)單詞并捕獲,在至少一個(gè)空格后再取出其捕獲的內(nèi)容進(jìn)行匹配,這樣前后兩個(gè)匹配就是相同的\<\>又保障了這兩個(gè)是單詞,所以滿(mǎn)足需求。類(lèi)似的有(0-9)(a-z)\1\2 用于匹配連續(xù)兩次相同的數(shù)字加字母2a2a \1 取第一個(gè)()捕獲的內(nèi)容 \2取第二個(gè)()捕獲的內(nèi)容
如果不是在表達(dá)式中而是在表達(dá)式匹配完成時(shí)取得這些分組中的值,
不同語(yǔ)言有不同的方式,
Perl中用 $1 $2 去取分組\1 和 \2 的內(nèi)容,
Python中用group(1) group(2) 比如:
Perl中:以下表達(dá)式用來(lái)匹配前兩個(gè)數(shù)字相同后面跟多個(gè)字母的字符串 類(lèi)似99abc , 22kkk
if($inputStr =~ m/^([0-9])\1([a-zA-Z]*)$/) {
$value = $1; //$1 用來(lái)獲取第一個(gè)括號(hào)內(nèi)捕獲的內(nèi)容
$string = $2; //$2 用來(lái)獲取第二個(gè)括號(hào)內(nèi)捕獲的內(nèi)容
print "value = $value, string = $string";
}
當(dāng)()嵌套時(shí)其對(duì)應(yīng)的序號(hào)按照左邊半括號(hào)(的順序,
比如:(abc(123(.CF)))
abc(123(.CF)) 在分組\1內(nèi) 123(.CF)在分組\2內(nèi) .CF在分組\3內(nèi)
這里有個(gè)()的反例,如果只想界定范圍而不捕捉內(nèi)容怎么做,答案是:(?:) 這是一個(gè)組合元字符 里面的 ? 和之前的 可選? 沒(méi)有任何關(guān)系
把上面的例子改為 (abc(?:123(.CF))) 則分組順序變成了 abc(123(.CF)) 在分組\1內(nèi) 由于第二個(gè)( 放棄了捕捉內(nèi)容因此123(.CF)不會(huì)被捕捉,也不占用分組,所以.CF 變成了在分組\2內(nèi) 這個(gè)元字符在這個(gè)例子里也許并看不出有什么實(shí)際意義,但是在一些實(shí)際的開(kāi)發(fā)場(chǎng)景里非常有用,舉個(gè)例子:
你在開(kāi)發(fā)中寫(xiě)了這么一個(gè)正則 ([0-9]+)\s*(K?m) 用來(lái)捕獲距離,\1 捕獲數(shù)值 \2 捕獲單位,而且此時(shí)程序已經(jīng)寫(xiě)完了,這時(shí)需求擴(kuò)展了,需要能匹配小數(shù),所以你把正則改為了 ([0-9]+(.[0-9])?)\s*(K?m) 這里 (.[0-9])?的括號(hào)原本只是用來(lái)給?界定邊界的 但是其依然有捕捉作用,如果你這么寫(xiě) 因?yàn)椴迦肓艘粋€(gè)分組 原本用來(lái)捕捉單位的分組則變成了\3 因?yàn)榇藭r(shí)程序已經(jīng)寫(xiě)完了,內(nèi)部都是按照\(chéng)2判斷的,所以要想程序正常就必須把之前里面對(duì)\2的判斷改為\3 這當(dāng)然是可行的,但是還有一種更優(yōu)雅的方法就是讓不必捕捉的括號(hào)放棄捕捉,正則表達(dá)式改為([0-9]+(?:.[0-9])?)\s*(K?m) 這里只是多了個(gè)?: 由于第二個(gè)(內(nèi)放棄了捕捉,不占用分組,所以原來(lái)的分組順序沒(méi)有任何改變就能完成這個(gè)功能的擴(kuò)展
反斜杠 \ 轉(zhuǎn)義
前面有多次出現(xiàn)過(guò)反斜杠的地方 比如 \< \> \1 \2 這些都被成為組合元字符 他們組合在一起有特殊意義 其實(shí)反斜杠還有個(gè)非常重要的作用 轉(zhuǎn)義,比如我們想要匹配一個(gè) \< 的字符串要怎么做,因?yàn)閈<本身代表的是單詞開(kāi)頭 所以要把元字符當(dāng)成一個(gè)普通字符進(jìn)行匹配就需要用到轉(zhuǎn)義 在元字符前加一個(gè)\ 所以可以用\\< 匹配\<
正則表達(dá)式一些簡(jiǎn)記字符(Python支持)
\a 警報(bào)符
\f 進(jìn)止符
\b 單詞分界符(當(dāng)出現(xiàn)在字符組內(nèi)時(shí)為退格符[\b])
\v 垂直制表符
\t 水平制表符
\n 換行符
\r 回車(chē)符
\s 任何'空白'字符(空格,制表符,進(jìn)止符)*很常用
\S 除了 \s 之外的所有字符
\w [0-9a-zA-Z] (經(jīng)測(cè)試在Python3中 \w 表示[0-9a-zA-Z_]) 除了數(shù)字和字母外還會(huì)包括下劃線(xiàn)
\W 除了 \w 之外的所有字符即[^0-9a-zA-Z]
\d [0-9]
\D [^0-9]
環(huán)視
環(huán)視的中心思想是用來(lái)找出一個(gè)位置而不是字符,所以其只用來(lái)校驗(yàn)不用來(lái)捕獲,也不占用匹配字符
肯定環(huán)視:
順序環(huán)視 (?=...) 從左往右校驗(yàn) 當(dāng)遇到 滿(mǎn)足 條件的字段時(shí),那么該字段的左邊位置便是匹配到的位置
逆序環(huán)視 (?<=...) 從右往左校驗(yàn) 當(dāng)遇到 滿(mǎn)足 條件的字段時(shí),那么該字段的右邊位置便是匹配到的位置
比如 (?<=goo)(?=d) 這個(gè)表達(dá)式用來(lái)找到一個(gè) 在goo右邊
在 d 左邊的一個(gè)位置,如果這個(gè)位置存在則為true否則為false
(?=good)(goo) 這個(gè)表達(dá)式的作用是是找到字符串good左邊的一個(gè)位置并捕獲
這個(gè)位置 后的goo 即只匹配good 單詞中的goo 不會(huì)匹配 goof中的goo
(ood)(?<=good) 和上面一樣,ood后面的位置必須是good的右邊,
因此只匹配good中的ood 不會(huì)匹配bood中的ood
(?=good)(ood) 該表達(dá)式永遠(yuǎn)為false
因?yàn)?good 左邊位置的下一個(gè)字符是 g 而不是 o,
因此永遠(yuǎn)為false
同理:(goo)(?<=good) 也永遠(yuǎn)為false
因?yàn)?goo)的右邊緊鄰的是goo的右邊
而環(huán)視要求是good的右邊,這兩個(gè)條件矛盾,永不成立
否定環(huán)視:
否定順序環(huán)視 (?!...) 從左往右校驗(yàn) 當(dāng)遇到 不滿(mǎn)足 條件的字段時(shí),那么該字段的左邊位置便是匹配到的位置
否定逆序環(huán)視 (?<!...) 從右往左校驗(yàn) 當(dāng)遇到 不滿(mǎn)足 條件的字段時(shí),那么該字段的右邊位置便是匹配到的位置
同樣的例子:
(?!good)(goo) 先找到不是good字段的左邊的一個(gè)位置
并捕獲 這個(gè)位置 后的goo 即不匹配good中的goo 可以匹配 goof 中的goo
否定逆序同理
關(guān)于環(huán)視的主意點(diǎn):
在Python中逆序環(huán)視必須是有明確長(zhǎng)度的,
順序環(huán)視則沒(méi)有限制 比如 (?<=[a-z]+) 是不合法的,
因?yàn)閇a-z]+的長(zhǎng)度是不固定的 但在順序中可以 (?=[a-z]+) 是合法的
所以應(yīng)當(dāng)特別主意逆序中只能使用(?<=[a-z]) (?<=good)這種有明確長(zhǎng)度的表示
判斷條件 (?... ...|...) 相當(dāng)于 (?if then | else)
?后面緊跟判斷條件,條件成立則匹配 | 左邊 否則匹配其右邊
(?(?:good)[A-Z]|[a-z]) good后面應(yīng)該是一個(gè)大寫(xiě)字母 不是good則后面應(yīng)該是個(gè)小寫(xiě)字母
匹配優(yōu)先量詞
?,*,+,{}
匹配優(yōu)先的匹配過(guò)程是先滿(mǎn)足前面匹配條件,
并且盡可能多的匹配:
比如 .*: 去匹配abc:abc: 匹配到的內(nèi)容不是 abc: 而是abc:abc:
原因就是.* 是匹配優(yōu)先的,
會(huì)直接匹配所有字符即 abc:abc: 當(dāng)匹配進(jìn)行到':'時(shí).*已經(jīng)把所有字符匹配完了,
但為了滿(mǎn)足整個(gè)表達(dá)式,所以.*匹配的內(nèi)容要進(jìn)行回溯,回溯是后入先出的,即倒序的,
因此從第二個(gè):開(kāi)始,當(dāng)回溯到第二個(gè):時(shí)正好滿(mǎn)足表達(dá)式
忽略?xún)?yōu)先量詞
*?,??,+?,{}?
忽略?xún)?yōu)先的匹配過(guò)程是:
先跳過(guò)略?xún)?yōu)先的匹配進(jìn)行下面的匹配,
如果下面的匹配不符合就回溯到忽略匹配,
比如把上面的例子改為 .*?: 去匹配 abc:abc:
此時(shí)其匹配的結(jié)果便是 abc: 而不是 abc:abc:
原因是 *?是忽略?xún)?yōu)先的,因此在該匹配過(guò)程中會(huì)跳過(guò).的匹配 先匹配 ':'
第一個(gè)a不能滿(mǎn)足 : 就回溯到a并用.去匹配,
因?yàn)?可以匹配a所以接著用:進(jìn)行下面的匹配b 因?yàn)閎也滿(mǎn)足不了:
所以回溯到b并用忽略條件去匹配 依次類(lèi)推,
當(dāng)匹配到第一個(gè):時(shí)此時(shí)正則表達(dá)式已滿(mǎn)足,匹配完成
占有優(yōu)先量詞
*+,?+,++,{}+
占有優(yōu)先和匹配優(yōu)先一樣會(huì)先盡可能多的滿(mǎn)足前面的匹配條件,
而且一旦內(nèi)容被匹配就不會(huì)再交出來(lái),即不允許匹配到的內(nèi)容進(jìn)行回溯
還是上面的例子 .*+: 去匹配 abc:abc: 此時(shí)匹配會(huì)失敗
原因是 .*+ 會(huì)直接匹配到所有的內(nèi)容 abc:abc:
當(dāng)匹配到表達(dá)式的 : 時(shí)因?yàn)橐呀?jīng)沒(méi)有內(nèi)容了,
正常情況下需要對(duì)前面匹配到的內(nèi)容進(jìn)行回溯,
但前面的內(nèi)容是占有優(yōu)先的,不允許回溯,因此:無(wú)法匹配到內(nèi)容,所以匹配失敗
固化分組
(?>...)
固化分組的作用和占有優(yōu)先相同,即防止匹配的內(nèi)容回溯
比如 (?>.*): 匹配 abc:abc: 同樣會(huì)失敗,原因和占有優(yōu)先相同
回溯的補(bǔ)充:
關(guān)于回溯,其實(shí)回溯能夠?qū)崿F(xiàn)的原因是表達(dá)式引擎在匹配過(guò)程中的每一步都會(huì)保留一個(gè)備用狀態(tài),當(dāng)后面的匹配失敗時(shí)就會(huì)跳轉(zhuǎn)到上一個(gè)備用狀態(tài)即完成回溯
占有優(yōu)先和固化分組能夠避免回溯的原因是,占有優(yōu)先和固化分組的條件都不會(huì)保留備用狀態(tài),因此無(wú)法回溯
固化分組和占有優(yōu)先的意義在于可以?xún)?yōu)化表達(dá)式,避免非必要的回溯,比如 .*+: 去匹配 abcdefg 當(dāng)程序匹配到g時(shí)表達(dá)式立即給出失敗,因?yàn)?無(wú)法匹配,又無(wú)法回溯,
如果改為匹配優(yōu)先 .*: 當(dāng)匹配到g時(shí)此時(shí)表達(dá)式開(kāi)始匹配: 但是內(nèi)容已經(jīng)被.* 全部匹配了,所以要進(jìn)行回溯到g此時(shí)仍不匹配繼續(xù)回溯到f依次一直回溯到a發(fā)現(xiàn)表達(dá)式確實(shí)無(wú)法滿(mǎn)足,此時(shí)才會(huì)報(bào)告失敗
匹配優(yōu)化
因?yàn)镈FA是文本引導(dǎo)型,優(yōu)化表達(dá)式并無(wú)作用。而NFA則是表達(dá)式引導(dǎo)型,不同的表達(dá)式效率差別會(huì)非常大,但由于POSIX NFA效率低下和用到的很少,所以以下優(yōu)化沒(méi)有特殊說(shuō)明則只針對(duì)傳統(tǒng)NFA引擎。
對(duì)于多選結(jié)構(gòu)來(lái)說(shuō)一般把更加通用的匹配條件放到前面,會(huì)減少回溯次數(shù)
例如用(^"|")* 來(lái)匹配 abcdefghigk"lmn 就比 ("|^")* 要快 因?yàn)閷?duì)于傳統(tǒng)NFA來(lái)說(shuō) 多選結(jié)構(gòu)只有第一個(gè)條件匹配失敗才會(huì)回溯去匹配下一個(gè)條件 對(duì)于第一個(gè)表達(dá)式(^")* 第一個(gè)條件可以一直匹配到k 直到第一個(gè)"才會(huì)匹配失敗,此時(shí)回溯用(")來(lái)匹配 接下來(lái)繼續(xù)用(^")匹配到最后 因此第一個(gè)表達(dá)式只進(jìn)行了一次回溯。而第二個(gè)表達(dá)式("|^")* 需要先用(")* 來(lái)匹配,因?yàn)橐恢钡?之前都不滿(mǎn)足因此a-k每次匹配都要回溯后用(^")* 來(lái)匹配 這樣整個(gè)表達(dá)式匹配完成需要14次回溯
對(duì)于上面的例子還可以進(jìn)一步優(yōu)化,(^"|")* 雖然可以減少回溯次數(shù),但仍需要15次迭代即匹配的次數(shù),為了減少迭代可以改寫(xiě)成(^"+|") 這樣 由于+是匹配優(yōu)先的 所以(^"+)會(huì)一次性匹配 a-k,然后回溯一次之后再一次性匹配 l-n 這樣整個(gè)表達(dá)式 只進(jìn)行了一次回溯 和 兩次迭代
這里要注意只有在傳統(tǒng)NFA引擎下才可以用(^"+|")*來(lái)優(yōu)化,
對(duì)于POSIX NFA來(lái)說(shuō)這會(huì)引起超線(xiàn)性匹配,
實(shí)際上對(duì)于任何量詞的嵌套POSIX NFA 都會(huì)產(chǎn)生超線(xiàn)性匹配
比如([a-z]+)* 來(lái)匹配 abcdefghigklmn
因?yàn)镻OSIX NFA 下表達(dá)式并不會(huì)在第一次匹配成功后就停止,
而是會(huì)遍歷所有的可能 這樣由于+和*都是不定長(zhǎng)量詞,
因此表達(dá)式會(huì)嘗試(a)bcdefghigklmn 也會(huì)嘗試 (ab)cdefghigklmn
也會(huì)嘗試 a(bc)defghigklmn 依次類(lèi)推,
這個(gè)匹配數(shù)量是冪增長(zhǎng)的 當(dāng)字符串長(zhǎng)度超過(guò)40時(shí)將是一個(gè)天文數(shù)字對(duì)于
對(duì)于相似單詞的匹配多選分支的范圍盡量要小
比如匹配this 或 that 表達(dá)式 th(is|at) 要比 (this|that) 好
單字符多選分支可采用字符集來(lái)替代 對(duì)于指定字符可采用簡(jiǎn)記符來(lái)代替 即 \d > [0-9] > (0|1|2|3|4|5|6|7|8|9)
另一種優(yōu)化
對(duì)于正則引擎,每次匹配都需要編譯表達(dá)式進(jìn)行格式差錯(cuò)校驗(yàn),校驗(yàn)完成才會(huì)調(diào)動(dòng)傳動(dòng)引擎進(jìn)行匹配,對(duì)于不含變量的表達(dá)式這樣重復(fù)校驗(yàn)完全是沒(méi)必要的,可以進(jìn)行預(yù)編譯并緩存,python 對(duì)預(yù)編譯的支持是通過(guò) compile()來(lái)實(shí)現(xiàn)的
未完待續(xù)...
上面內(nèi)容均來(lái)自《正則表達(dá)式》一書(shū),在接下來(lái)的學(xué)習(xí)中本人任然會(huì)把一些心得和總結(jié)在該文章中進(jìn)行分享,希望對(duì)大家有些許的幫助