正則表達式(二)

`>本文是 Jan Goyvaerts 為 RegexBuddy 寫的教程的譯文盈电,版權(quán)歸原作者所有

在本文中講述了正則表達式中的:

向后引用
先前向后查看
條件測試
單詞邊界
選擇符
等表達式及例子,并分析了正則引擎在執(zhí)行匹配時的內(nèi)部機理冶匹。

單詞邊界

元字符\b是一種對位置進行匹配的“錨”陷虎。這種匹配是 0 長度匹配皆警。

有 4 種位置被認為是“單詞邊界”:

  1. 如果字符串的第一個字符是一個“單詞字符”凰浮,在字符串的第一個字符前的位置
  2. 如果字符串的最后一個字符是一個“單詞字符”膜廊,在字符串的最后一個字符后的位置
  3. “非單詞字符”緊跟在“單詞字符”之后時,在“單詞字符”和“非單詞字符”之間
  4. “單詞字符”緊跟在“非單詞字符”之后時冲粤,在“非單詞字符”和“單詞字符”之間

“單詞字符” 用“\w”匹配的字符
“非單詞字符” 用“\W”匹配的字符

在大多數(shù)的正則表達式實現(xiàn)中,“單詞字符”通常包括[a-zA-Z0-9_]


正則表達式\b4\b
能匹配單個的 4 ,而不是一個更大數(shù)的一部分(不會匹 配44中的 4)
即幾乎可以說\b匹配一個“字母數(shù)字序列”的開始和結(jié)束的位置页眯。

“單詞邊界”的取反集為\B
他要匹配的位置是兩個“單詞字符”之間或者兩個“非單詞字符”之間的位置梯捕。

深入正則表達式引擎內(nèi)部


正則表達式\bis\b
字符串This island is beautiful

引擎 先處理符號\b
因為\b 是 0 長度 ,所以第一個字符 T 前面的位置會被檢查窝撵。
T 是一個“單詞字符” 且它之前的字符是一個空字符(void)傀顾,這是一個單詞邊界,\b匹配成功
但正則表達式中的i和第1個字符T匹配失敗碌奉,回溯~

單詞邊界\b繼續(xù)匹配短曾,第5個空格符和第4個字符s之間是一個單詞邊界,\b匹配成功
但正則表達式中的i和第5個空格符匹配失敗赐劣,回溯~

單詞邊界\b繼續(xù)匹配嫉拐,第5個空格字符和第6個字符i之間是一個單詞邊界,\b匹配成功
正則表達式is和第6個第7個字符匹配成功
但第8個字符l不被單詞邊界\b匹配魁兼,匹配失敗婉徘,回溯~

單詞邊界\b繼續(xù)匹配,到了第 13 個字符i和前面一個空格符形成“單詞邊界”,同時isis匹配盖呼。正則表達式中第二個\b開始匹配儒鹿,
單詞s和他之后的空格符是一個單詞邊界,\b匹配成功几晤。
正則表達式結(jié)束约炎。
引擎“急著”返回成功匹配的結(jié)果。

選擇符

正則表達式中“|”表示選擇蟹瘾。你可以用選擇符匹配多個可能的正則表達式中的一個圾浅。
如果你想匹配文字“cat”或“dog”
正則表達式cat|dog
如果想多匹配就加入即可cat|dog|mouse|fish

選擇符在正則表達式中具有最低的優(yōu)先級,即它告訴引擎热芹,要么匹配選擇符左邊的所有表達式贱傀,要么匹配右邊的所有表達式。

你也可以用圓括號來限制選擇符的作用范圍伊脓。

\b(cat|dog)\b這樣告訴正則引擎把(cat|dog)當成一個正則表達式單位來處理府寒。

正則引擎是急切的:當它找到一個有效的匹配時,停止搜索报腔。
因此在一定條件下株搔,選擇符兩邊的表達式的順序?qū)Y(jié)果會有影響。


用正則表達式搜索一個編程語言的函數(shù)列表
Get 或 GetValue 或 Set 或 SetValue
一個明顯的解決方案是正則表達式Get|GetValue|Set|SetValue
結(jié)果
因為正則表達式GetGetValue都失敗了纯蛾,而Set匹配成功纤房。因為正則導向的引擎都是“急切”的,所以它會返回第一個成功的匹配翻诉,文本Set炮姨,而不去繼續(xù)搜索是否有其他更好的匹配。
和我們期望的相反碰煌,正則表達式并沒有匹配整個字符串舒岸。有幾種可能的解決辦法。
1.改變選項的順序芦圾,例如我們使用正則表達式GetValue|Get|SetValue|Set這樣我們就可以優(yōu)先搜索最長的匹配蛾派。
2.把四個選項結(jié)合起來成兩個選項Get(Value)?|Set(Value)?
因為問號重復符是貪婪的, 所以 SetValue 總會在 Set 之前被匹配个少。
3.更好的方案是. 使用單詞邊界
\b(Get|GetValue|Set|SetValue)\b\b(Get(Value)?|Set(Value)?\b
既然所有的選擇都有相同的結(jié)尾洪乍,正則表達式可優(yōu)化為\b(Get|Set)(Value)?\b

組與向后引用

把正則表達式的一部分放在圓括號內(nèi),你可以將它們形成組夜焦。
然后你可以對整個組使用一些正則操作壳澳,例如重復操作符。

注意區(qū)別
()圓括號用于形成正則表達式組
[]用于定義字符集
{}用于定義重復操作

當用()定義了一個正則表達式組后糊探,正則引擎則會把被匹配的組按照順序編號钾埂,存入緩存河闰。
當對被匹配的組進行向后引用的時候,可以用“\數(shù)字”的方式進行引用褥紫。
正則表達式\1引用第1個匹配的后向引用組姜性,\2引用第2組,以此類推髓考,\n引用第 n 個組部念。
\0則引用整個正則表達式本身。

假設你想匹配一個 HTML 標簽的開始標簽和結(jié)束標簽氨菇,以及標簽中間的文本儡炼。

要匹配<B>和</B>以及中間的文字。
文本<B>This is a test</B>
正則表達式<([A-Z][A-Z0-9]*)[^>]*>.*?</\1>

首先查蓉,正則表達式<將會匹配第一個文本字符<
然后乌询,正則表達式[A-Z]匹配文本B
[A-Z0-9]*匹配 0 到多次字母數(shù)字,后面緊接著 非“>”的字符 0個到多個豌研。
最后妹田,正則表達式的>將會匹配文本<B>

接下來正則引擎將對結(jié)束標簽之前的字符進行惰性匹配(急于表功,為了找到最短的文本鹃共,每次.*?匹配成功后都試圖進行正則表達式</的匹配鬼佣。
然后正則表達式中的“\1”表示對前面匹配的組([A-Z][A-Z0-9]*)進行引用,在本例中霜浴,被引用的是標簽名 即 文本字符B,所以需要被匹配的結(jié)尾標簽為</B>

可以多次引用相同的后向引用組
正則表達式([a-c])x\1x\1
會匹配文本
axaxa
bxbxb
cxcxc

如果用數(shù)字形式引用的組沒有有效的匹配晶衷,則引用到的內(nèi)容簡單的為空。
一個后向引用不能用于它自身阴孟。

錯誤正則表達式([abc]\1)
不能將\0用于一個正則表達式匹配本身晌纫,它只能用于替換操作中。

后向引用不能用于字符集內(nèi)部永丝。
[]包含的字符集內(nèi)部 \1被解釋為八進制形式的轉(zhuǎn)碼缸匪。
所以像正則表達式 (a)[\1b] 其中的\1并不表示后向引用。

向后引用會降低引擎的速度类溢,因為它需要存儲匹配的組。
如果你不需要向后引用露懒,你可以告訴引擎對某個組不存儲闯冷。例如Get(?:Value)
其中(后面緊跟的?:會告訴引擎對于組(Value)不存儲匹配的值以供后向引用。

重復操作與后向引用

當對組使用重復操作符時懈词,緩存里后向引用內(nèi)容會被不斷刷新蛇耀,只保留最后匹配的內(nèi)容。


正則表達式([abc]+)=\1
可以匹配文本cab=cab

但正則表達式([abc])+=\1不會匹配文本cab=cab

因為
([abc])第一次匹配文本c時坎弯,\1已經(jīng)代表的是c
([abc])繼續(xù)匹配到了文本a \1已經(jīng)代表的是a
([abc])繼續(xù)匹配到了文本b 最后\1已經(jīng)代表的是b
所以正則表達式([abc])+=\1只會匹配到文本cab=b

應用:檢查重復單詞
當編輯文字時纺涤,很容易就會輸入重復單詞如the the
使用 \b(\w+)\s+\1\b可以檢測到這些重復單詞译暂。
要刪除第二個單詞,只要簡單的利用替換功能替換掉“\1”即可

組的命名和引用

在 PHP撩炊,Python 中外永,可以用(?P<name>group)來對組進行命名。
本例中?P<name>就是把組(group)命名為name
可以用 (?P=name)進行引用

.NET 的命名組
.NET framework 也支持命名組拧咳。不幸的是伯顶,微軟的程序員們決定發(fā)明他們自己的語法, 而不是沿用 Perl骆膝、Python 的規(guī)則祭衩。目前為止,還沒有任何其他的正則表達式實現(xiàn)支持微軟發(fā)明的語法阅签。
.NET 例
(?<first>group)(?’second’group)
正如你所看到的掐暮,.NET 供兩種詞法來創(chuàng)建命名組:
用尖括號<> 在字符串中使用更方便
用單引號. 在 ASP 代碼中更有用 因為ASP代碼中<>被用作 HTML 標簽。

引用命名組
\k<name>\k’name’
當進行搜索替換時政钟,用${name}來引用一個命名組路克。

正則表達式的匹配模式

正則表達式引擎都支持三種匹配模式
/i使正則表達式對大小寫不敏感
/s開啟“單行模式”,即點號.匹配換行符(nweline)
/m開啟“多行模式”锥涕,即^$匹配換行符(nweline)的前面和后面的位置衷戈。

在正則表達式內(nèi)部打開或關(guān)閉模式
如果你在正則表達式內(nèi)部插入修飾符(?ism)
則該修飾符只對其右邊的正則表達式起作用。 (?-i)是關(guān)閉大小寫不敏感层坠。你可以很快的進行測試殖妇。
(?i)te(?-i)st應該匹配 TEst,但不能匹配 teST 或 TEST

原子組與防止回溯

一些特殊情況下回溯會使得引擎的效率極其低下破花。

要匹配這樣的字串谦趣,字串中的每個字段間用逗號做分隔符,第 12 個字段由P開頭座每。
容易想到這樣的正則表達式^(.*?,){11}P
這個正則表達式在正常情況下工作的很好前鹅。
但如果第 12 個字段不是由 P 開頭,則會發(fā)生災難性的回溯峭梳。
如文本
1,2,3,4,5,6,7,8,9,10,11,12,13

首先舰绘,正則表達式一直成功匹配直到第 12 個字符。這時葱椭,前面的正則表達式消耗的字串為1,2,3,4,5,6,7,8,9,10,11,
正則表達式中的P并不匹配12 引擎進行回溯捂寿,這時正則表達式消耗的字串為 1,2,3,4,5,6,7,8,9,10,11
繼續(xù)下一次匹配過程,下一個正則符號為點號. 能匹配下一個逗號,
,并不匹配字符12中的1 匹配失敗孵运,繼續(xù)回溯秦陋。
... 這樣的回溯組合是個非常大的數(shù)量 可能會造成引擎崩潰

用于阻止這樣巨大的回溯有方案:
1.簡單的方案 盡可能的使匹配精確
用取反字符集代替點號。例如我們用如下正則表達 式^([^,\r\n]*,){11}P
這樣可以使失敗回溯的次數(shù)下降到 11 次治笨。

2.使用原子組
原子組的目的是使正則引擎失敗的更快一點驳概。因此可以有效的阻止海量回溯赤嚼。原子組的語法 是(?>正則表達式)
位于(?>)之間的所有正則表達式都會被認為是一個單一的正則符號。 一旦匹配失敗顺又,引擎將會回溯到原子組前面的正則表達式部分更卒。前面的例子用原子組可以表達成^(?>(.*?,){11})P一旦第十二個字段匹配失敗,引擎回溯到原子組前面的^

向前查看與向后查看

Perl 5 引入了兩個強大的正則語法:“向前查看”和“向后查看”
他們也被稱作“零長度斷言”待榔。他們和錨定一樣都是 零長度的(即該正則表達式不消耗被匹配的字符串)
不同之處在于“前后查看”會實際匹配字符逞壁,只是他們會拋棄匹配只返回匹配結(jié)果:匹配或不匹配。這就是為什么他們被稱作“斷言”锐锣。他們并不實際消耗字符串中的字符腌闯,而只是斷言一個匹配是否可能。
注意:Javascript 只支持向前查看雕憔,不支持向后查看姿骏。

肯定和否定式的向前查看

前面的例子
要查找一個 q,后面沒有緊跟一個 u
即 要么 q 后面沒有字符斤彼,要么后面的字符不是 u
采用否定式向前查看后的一個解決方案為q(?!u)
否定式向前查看的語法是(?!查看的內(nèi)容)

肯定式向前查看和否定式向前查看很類似:?=查看的內(nèi)容)

如果在“查看的內(nèi)容”部分有組分瘦,也會產(chǎn)生一個向后引用。但是向前查看本身并不會產(chǎn)生向后引用琉苇,也不會被計入向后引用的編號中嘲玫。這是因為向前查看本身是會被拋棄掉的,只保留匹配與否的判斷結(jié)果并扇。如果你想保留匹配的結(jié)果作為向后引用去团,你可以用(?=(regex))來產(chǎn)生一個向后引用。

肯定和否定式的先后查看

向后查看和向前查看有相同的效果穷蛹,只是方向相反 否定式向后查看的語法是:<<(?<!查看內(nèi)容)>> 肯定式向后查看的語法是:<<(?<=查看內(nèi)容)>> 我們可以看到土陪,和向前查看相比,多了一個表示方向的左尖括號肴熏。 例:<<(?<!a)b>>將會匹配一個沒有“a”作前導字符的“b”鬼雀。 值得注意的是:向前查看從當前字符串位置開始對“查看”正則表達式進行匹配;向后查
看則從當前字符串位置開始先后回溯一個字符,然后再開始對“查看”正則表達式進行匹配蛙吏。

深入正則表達式引擎內(nèi)部

簡單例子
把正則表達式q(?!u)應用到字符串Iraq
正則表達式的第一個符號是q
開始匹配源哩,當?shù)谒膫€字符q被匹配后, q后面是空字符(void)
而下一個正則符號是向前查看鸦做。引擎注意到已經(jīng)進入了一個向前查看正則表達式部分璧疗。下一個正則符號u和空字符不匹配,從而導致向前查看里的正則表達式匹配失敗馁龟。因為是一個否定式的向前查看,意味著整個向前查看結(jié)果是成功的漆魔。于是匹配 結(jié)果q被返回了坷檩。

我們在把相同的正則表達式應用到文本quit
正則表達式q匹配了q 下一個正則符號是向前查看部分的正則表達式u
它匹配了字符串中的第二個字符i 引擎繼續(xù)走到下個字符i
引擎這時注意到向前查看部分已經(jīng)處理完了却音,并且向前查看已經(jīng)成功。于是引擎拋棄被匹配的字符串部分矢炼,這將導致引擎回退到字符u

因為向前查看是否定式的系瓢,意味著查看部分的成功匹配導致了整個向前查看的失敗,因此 引擎不得不進行回溯句灌。最后因為再沒有其他的文本q和正則表達式q匹配夷陋,所以整個匹配失敗了。

為了確保你能清楚地理解向前查看的實現(xiàn)胰锌,讓我們把正則表達式q(?=u)i應用到文本quit
正則表達式q首先匹配q
然后向前查看成功匹配u 匹配的部分被拋棄骗绕,只返回可以匹配的判斷結(jié)果。引擎從字符i回退到u

由于向前查看成功了资昧,引擎繼續(xù)處理下一個正則符號<<i>>酬土。 結(jié)果發(fā)現(xiàn)<<i>>和“u”不匹配。因此匹配失敗了格带。由于后面沒有其他的“q”撤缴,整個正則表達 式的匹配失敗了。
更進一步理解正則表達式引擎內(nèi)部機制
讓我們把<<(?<=a)b>>應用到“thingamabob”叽唱。引擎開始處理向后查看部分的正則 符號和字符串中的第一個字符屈呕。在這個例子中,向后查看告訴正則表達式引擎回退一個字符棺亭,然 后查看是否有一個“a”被匹配虎眨。因為在“t”前面沒有字符,所以引擎不能回退侦铜。因此向后查看 失敗了专甩。引擎繼續(xù)走到下一個字符“h”。再一次钉稍,引擎暫時回退一個字符并檢查是否有個“a” 被匹配涤躲。結(jié)果發(fā)現(xiàn)了一個“t”。向后查看又失敗了贡未。
向后查看繼續(xù)失敗种樱,直到正則表達式到達了字符串中的“m”壳坪,于是肯定式的向后查看被 匹配了陡叠。因為它是零長度的,字符串的當前位置仍然是“m”绘沉。下一個正則符號是<<b>>消恍,和 “m”匹配失敗岂昭。下一個字符是字符串中的第二個“a”。引擎向后暫時回退一個字符狠怨,并且發(fā) 現(xiàn)<<a>>不匹配“m”约啊。
在下一個字符是字符串中的第一個“b”邑遏。引擎暫時性的向后退一個字符發(fā)現(xiàn)向后查看被滿 足了,同時<<b>>匹配了“b”恰矩。因此整個正則表達式被匹配了记盒。作為結(jié)果,正則表達式返回 字符串中的第一個“b”外傅。
向前向后查看的應用
我們來看這樣一個例子:查找一個具有 6 位字符的纪吮,含有“cat”的單詞。 首先萎胰,我們可以不用向前向后查看來解決問題碾盟,例如:
<< cat\w{3}|\wcat\w{2}|\w{2}cat\w|\w{3}cat>> 足夠簡單吧!但是當需求變成查找一個具有 6-12 位字符,含有“cat”奥洼,“dog”或“mouse”
的單詞時巷疼,這種方法就變得有些笨拙了。
我們來看看使用向前查看的方案灵奖。在這個例子中嚼沿,我們有兩個基本需求要滿足:一是我們
需要一個 6 位的字符,二是單詞含有“cat”瓷患。 滿足第一個需求的正則表達式為<<\b\w{6}\b>>骡尽。滿足第二個需求的正則表達式為
<<\b\wcat\w\b>>。
把兩者結(jié)合起來擅编,我們可以得到如下的正則表達式:
<<(?=\b\w{6}\b)\b\wcat\w\b>>
具體的匹配過程留給讀者攀细。但是要注意的一點是,向前查看是不消耗字符的爱态,因此當判斷 單詞滿足具有 6 個字符的條件后谭贪,引擎會從開始判斷前的位置繼續(xù)對后面的正則表達式進行匹 配。
最后作些優(yōu)化锦担,可以得到下面的正則表達式:
<<\b(?=\w{6}\b)\w{0,3}cat\w*>>

  1. 正則表達式中的條件測試 條件測試的語法為<<(?ifthen|else)>>俭识。“if”部分可以是向前向后查看表達式洞渔。如果用
    向前查看套媚,則語法變?yōu)?<<(?(?=regex)then|else)>>,其中 else 部分是可選的磁椒。
    如果 if 部分為 true堤瘤,則正則引擎會試圖匹配 then 部分,否則引擎會試圖匹配 else 部分浆熔。 需要記住的是本辐,向前先后查看并不實際消耗任何字符,因此后面的 then 與 else 部分的匹
    配時從 if 測試前的部分開始進行嘗試。
  2. 為正則表達式添加注釋 在正則表達式中添加注釋的語法是:<<(?#comment)>> 例:為用于匹配有效日期的正則表達式添加注釋:
    (?#year)(19|20)\d\d- /.(0[1-9]|1[012])- /.(0[1-9]|[12][0-9]|3[01])
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慎皱,一起剝皮案震驚了整個濱河市环葵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宝冕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓萨,死亡現(xiàn)場離奇詭異地梨,居然都是意外死亡,警方通過查閱死者的電腦和手機缔恳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門宝剖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歉甚,你說我怎么就攤上這事万细。” “怎么了纸泄?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵赖钞,是天一觀的道長。 經(jīng)常有香客問我聘裁,道長雪营,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任衡便,我火速辦了婚禮献起,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘镣陕。我一直安慰自己谴餐,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布呆抑。 她就那樣靜靜地躺著岂嗓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪理肺。 梳的紋絲不亂的頭發(fā)上摄闸,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音妹萨,去河邊找鬼年枕。 笑死,一個胖子當著我的面吹牛乎完,可吹牛的內(nèi)容都是我干的熏兄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摩桶!你這毒婦竟也來了桥状?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤硝清,失蹤者是張志新(化名)和其女友劉穎辅斟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芦拿,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡士飒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔗崎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酵幕。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缓苛,靈堂內(nèi)的尸體忽然破棺而出芳撒,到底是詐尸還是另有隱情,我是刑警寧澤未桥,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布笔刹,位于F島的核電站,受9級特大地震影響钢属,放射性物質(zhì)發(fā)生泄漏徘熔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一淆党、第九天 我趴在偏房一處隱蔽的房頂上張望酷师。 院中可真熱鬧,春花似錦染乌、人聲如沸山孔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽台颠。三九已至,卻和暖如春勒庄,著一層夾襖步出監(jiān)牢的瞬間串前,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工实蔽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荡碾,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓局装,卻偏偏與公主長得像坛吁,于是被迫代替她去往敵國和親劳殖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容