前言
本文并非原創(chuàng)亮曹,內(nèi)容分別摘自維基百科橄杨、《精通正則表達(dá)式》第三版秘症、正則表達(dá)式30分鐘入門(mén)教程。
什么是正則表達(dá)式
在理論計(jì)算機(jī)科學(xué)和形式語(yǔ)言理論中式矫,正則表達(dá)式是定義了一個(gè)檢索模式的字符串乡摹。由字符串搜索算法在文本中檢索、替換匹配正則表達(dá)式定義的字符串采转。[1]
一個(gè)簡(jiǎn)單的例子
假設(shè)需在一段文本中查找?guī)в衧rc屬性的img標(biāo)簽聪廉,可以使用正則表達(dá)式<img\s+?src=(["'])[^\1]*?\1\s*?/?>
。
這條正則(<small>為了表述的流暢性故慈,下文將“正則表達(dá)式”簡(jiǎn)稱(chēng)為“正則”</small>)描述了這樣一個(gè)檢索模式:以<img
開(kāi)頭板熊,以>
結(jié)尾,<img
與src=
間有若干(大于0)空白符察绷,src=
與>
間同樣有若干(大于等于0)空白符干签,且>
前可能存在一斜杠/
,同時(shí)src=
后緊跟一對(duì)引號(hào)(可能是單引號(hào)拆撼,也可能是雙引號(hào))容劳,且這對(duì)引號(hào)之間有若干(大于等于0)個(gè)外括引號(hào)以外的任意字符。
元字符
一條正則通常由兩種字符構(gòu)成:元字符及普通字符闸度。通辰叻罚可以將元字符看做普通語(yǔ)言中的語(yǔ)法,它為正則表達(dá)式提供了強(qiáng)大的描述能力莺禁。
行錨點(diǎn)
^
代表行的開(kāi)始留量,它將匹配文本錨定在行首位置,即^cat
只匹配行首的cat。
$
代表行的結(jié)束肪获,它將匹配文本錨定在行尾位置寝凌,即cat$
只匹配位于行尾的cat,如以scat結(jié)尾的行孝赫。
^
和$
都只匹配位置较木,而不是具體的文本。在某些不支持處理多行的場(chǎng)景里青柄,^
和$
的意義則變?yōu)槠ヅ渥址拈_(kāi)始和結(jié)束伐债。
單詞分界符
\b
代表單詞的開(kāi)頭或者結(jié)尾,即單詞的分界處致开,可以將它看作單詞版本的行錨點(diǎn)峰锁,如\bcat\b
表示“匹配cat這個(gè)單詞”。同理双戳,它只匹配位置虹蒋。
字符組
字符組,即結(jié)構(gòu)體[···]
飒货,允許使用者列出在某處期望匹配的字符魄衅。如gr[ea]y
的意思是:先找到g,然后是一個(gè)r塘辅,接著是e或者a晃虫,最后是一個(gè)y。字符組的內(nèi)容是在同一個(gè)位置能夠匹配若干字符扣墩,它的意思是“或”(前例中的gr[ea]y
在效果層面等同于gr(e|a)y
)哲银;而在字符組之外的普通字符必須順序匹配,有“接下來(lái)是”的意思呻惕。
在字符組內(nèi)部荆责,-
可以表示為一個(gè)范圍,當(dāng)且僅當(dāng)此時(shí)亚脆,-
才是元字符做院,若它出現(xiàn)在字符組的首尾位置,則僅表示為一個(gè)普通字符型酥。同理山憨,?
和.
在字符組外常被當(dāng)做元字符,但是在字符組內(nèi)則不是如此弥喉。
排除型字符組
排除型字符組郁竟,即[^···]
,允許使用者列出在某處不希望匹配的字符由境。換言之棚亩,這個(gè)字符組會(huì)匹配任何未列出的字符蓖议。這個(gè)字符組開(kāi)頭的^
表示“排除”,與在字符組外^
表示行錨點(diǎn)的意義截然不同讥蟆。
多選結(jié)構(gòu)
|
勒虾,相當(dāng)于邏輯運(yùn)算中的或運(yùn)算,把不同的子表達(dá)式組合成一個(gè)總表達(dá)式瘸彤,這個(gè)總表達(dá)式能夠匹配任意的子表達(dá)式修然。在這樣的組合中,子表達(dá)式稱(chēng)為“多選分支”质况。
gr[ea]y
和gr(e|a)y
雖然在效果上表現(xiàn)相同愕宋,但是字符組和多選結(jié)構(gòu)在本質(zhì)上是兩個(gè)概念。字符組只能匹配目標(biāo)文本中的單個(gè)字符结榄,而每個(gè)多選結(jié)構(gòu)自身都可能是一條完整的正則表達(dá)式中贝。
分組與反向引用
圓括號(hào)將限定的若干字符組合成一個(gè)子表達(dá)式,作為一個(gè)分組臼朗。默認(rèn)情況下邻寿,分組會(huì)“記住”子表達(dá)式匹配的文本供表達(dá)式或其他過(guò)程使用,又稱(chēng)為捕獲视哑。每個(gè)分組都有一個(gè)組號(hào)绣否,組號(hào)由左往右從1開(kāi)始分配。
反向引用則用于重復(fù)檢索之前某個(gè)分組匹配的文本黎炉,如\1
代表分組1匹配的文本枝秤。
若僅希望子表達(dá)式進(jìn)行匹配醋拧,而不捕獲匹配的文本慷嗜,也不給此分組分配組號(hào),則可以在圓括號(hào)內(nèi)的子表達(dá)式前加上?:
丹壕,如:(?:expression)
庆械。
轉(zhuǎn)義
若想匹配元字符本身,如.
或*
菌赖,則需要通過(guò)\\
對(duì)字符進(jìn)行轉(zhuǎn)義缭乘,取消這些字符在正則中的特殊意義。
常用元字符
代碼 | 說(shuō)明 | |
---|---|---|
^ | 匹配行或字符串的開(kāi)始 | |
$ | 匹配行或字符串的結(jié)束 | |
. | 匹配除換行符以外的任意字符 | |
[···] | 匹配列出的任意字符 | |
[^···] | 匹配未列出的任意字符 | |
匹配分隔兩邊的任意表達(dá)式 | ||
(···) | 限定多選結(jié)構(gòu)范圍琉用,標(biāo)注量詞作用的元素堕绩,為反向引用“捕獲”文本 | |
\1,\2,... | 匹配之前的第一、第二等分組內(nèi)匹配的文本 | |
\b | 匹配單詞的開(kāi)始或結(jié)束 | |
\w | 匹配字母邑时、數(shù)字奴紧、下劃線(xiàn)、漢字 | |
\s | 匹配任意的空白符 | |
\d | 匹配數(shù)字 | |
\W | 匹配任意非字母晶丘、數(shù)字黍氮、下劃線(xiàn)唐含、漢字的字符 | |
\S | 匹配任意非空白符的字符 | |
\D | 匹配非數(shù)字的字符 | |
\B | 匹配非單詞開(kāi)始或結(jié)束的位置 |
重復(fù)限定符
代碼 | 說(shuō)明 |
---|---|
* | 重復(fù)零次或更多次 |
+ | 重復(fù)一次或更多次 |
? | 重復(fù)零次或一次 |
{n} | 重復(fù)n次 |
{min,} | 重復(fù)min次或更多次 |
{min,max} | 重復(fù)min到max次 |
?
代表可選項(xiàng),將它加在某個(gè)字符后面沫浆,表示此處容許出現(xiàn)該字符捷枯,但它的出現(xiàn)并非匹配成功的必要條件。u?
是必然能匹配成功的专执,有時(shí)它會(huì)匹配一個(gè)u淮捆,其他時(shí)候則不匹配任何字符,例如u?
在semicolon中匹配成功10處本股,但什么字符都沒(méi)有匹配争剿。
貪婪與懶惰
當(dāng)正則中包含重復(fù)限定符時(shí),默認(rèn)匹配模式是貪婪模式痊末,即盡可能多的字符蚕苇。若需要匹配盡可能少的字符時(shí),只需在重復(fù)限定符后加上?
凿叠,即可將貪婪模式轉(zhuǎn)換成懶惰模式涩笤。如用表達(dá)式a.*b
(貪婪)檢索aabab時(shí),會(huì)匹配整個(gè)字符串盒件,而用表達(dá)式a.*?b
(懶惰)檢索時(shí)蹬碧,會(huì)匹配aab(前三個(gè)字符)和ab(后兩個(gè)字符)。
代碼 | 說(shuō)明 |
---|---|
*? | 重復(fù)零次或更多次炒刁,但盡可能少重復(fù) |
+? | 重復(fù)一次或更多次恩沽,但盡可能少重復(fù) |
?? | 重復(fù)零次或一次,但盡可能少重復(fù) |
{min,}? | 重復(fù)min次或更多次翔始,但盡可能少重復(fù) |
{min,max}? | 重復(fù)min到max次罗心,但盡可能少重復(fù) |
零寬斷言
零寬斷言是指匹配寬度為零,滿(mǎn)足一定的斷言城瞎。斷言用來(lái)聲明一個(gè)應(yīng)該為真的事實(shí)渤闷,正則表達(dá)式中只有斷言為真時(shí)才會(huì)繼續(xù)匹配。而零寬斷言則用于檢索在某些內(nèi)容(但并不包含這些內(nèi)容)之前或之后的東西脖镀。像\b
飒箭、^
、$
一樣蜒灰,零寬斷言用于指定一個(gè)位置弦蹂,這個(gè)位置應(yīng)該滿(mǎn)足一定的條件(即斷言)。
零寬正預(yù)測(cè)先行斷言
零寬正預(yù)測(cè)先行斷言强窖,(?=expression)
凸椿,斷言自身出現(xiàn)位置的后面能匹配表達(dá)式expression。如\b\w+(?=ing\b)
可以匹配以ing結(jié)尾的單詞的前面部分(除了ing以外的部分)毕骡,在運(yùn)用這條正則檢索I'm singing while you're dancing.時(shí)削饵,它會(huì)匹配sing和danc岩瘦。
零寬正回顧后發(fā)斷言
零寬正回顧后發(fā)斷言,(?<=expression)
窿撬,斷言自身出現(xiàn)位置的前面能匹配表達(dá)式expression启昧。如(?<=\bre)\w+\b
可以匹配以re開(kāi)頭的后半部分(除了re以外的部分),在運(yùn)用這條正則檢索reading a book.時(shí)劈伴,它會(huì)匹配ading密末。
零寬負(fù)預(yù)測(cè)先行斷言
前文提到可以通過(guò)排除性字符組列出在某處不希望匹配的字符。若只想確保某個(gè)字符沒(méi)有出現(xiàn)跛璧,而不想匹配它严里,則可以使用負(fù)向零寬斷言解決這樣的問(wèn)題。比如追城,在需要查找這樣的單詞:它里面出現(xiàn)了q刹碾,但是q后面不能是u。用表達(dá)式\b\w*q[^u]\w*\b
檢索Iraq fighting時(shí)座柱,由于Iraq以q結(jié)尾的單詞迷帜,而[^u]
總要匹配一個(gè)字符,所以當(dāng)q作為單子的最后一個(gè)字符時(shí)色洞,[^u]
將會(huì)匹配q后面的單詞分隔符戏锹,后面的\w*\b
會(huì)匹配下一單詞,所以這條正則會(huì)匹配整個(gè)字符串火诸。正確的表達(dá)式為:\b\w*q(?!u)\w*\b
锦针。
零寬負(fù)預(yù)測(cè)先行斷言,(?!expression)
置蜀,斷言自身出現(xiàn)位置的后面不能匹配表達(dá)式expression奈搜。如\b((?!abc)\w)+\b
匹配不包含連續(xù)字符串abc的單詞。
零寬負(fù)回顧后發(fā)斷言
零寬負(fù)回顧后發(fā)斷言盾碗,(?<!expression)
媚污,斷言自身出現(xiàn)位置的前面不能匹配表達(dá)式expression舀瓢。如(?<![a-z])\d{7}
匹配前面不是小寫(xiě)字母的7位數(shù)字廷雅。
字符串搜索算法通常從左往右開(kāi)始檢索文本,當(dāng)前斷言位置的左邊京髓,即前文所述的“斷言自身出現(xiàn)位置的前面”航缀,而當(dāng)前斷言位置的右邊,即斷言的前行方向堰怨。
例1:(?<=\d)(?=(?:\d{3})+(?!\d))
匹配左邊有數(shù)字且右邊有3x個(gè)數(shù)字的位置芥玉;
例2:(?<=<(\w+)>)[\s\S]*?(?=<\/\1>)
匹配不包含屬性的簡(jiǎn)單HTML標(biāo)簽里的內(nèi)容。
注釋
圓括號(hào)的另一種用途是通過(guò)語(yǔ)法(?#comment)
來(lái)包含注釋?zhuān)?code>2[0-4]\d(?#200-249)备图。但并不是所有流派支持這種功能灿巧。
平衡組
當(dāng)需要匹配像<1 / <1 + 100> >(為方便描述赶袄,將算式中的圓括號(hào)用尖括號(hào)代替)這樣的可嵌套的層次性結(jié)構(gòu)時(shí),無(wú)論是使用<.+>
抠藕,還是<.+?>
都不能保證匹配到的內(nèi)容中的尖括號(hào)是逐層配對(duì)的饿肺。
這里需要用到平衡組,語(yǔ)法構(gòu)造如下:
-
(?'group'expression)
把捕獲的內(nèi)容命名為group盾似,并壓入堆棧敬辣。 -
(?'-group'expression)
從堆棧彈出名為group的捕獲內(nèi)容,若堆棧為空零院,則本分組匹配失敗溉跃。 -
(?(group)yes|no)
若堆棧上存在名為group的捕獲內(nèi)容的話(huà),則繼續(xù)匹配yes部分的表達(dá)式告抄,否則繼續(xù)匹配no部分的表達(dá)式撰茎。 -
(?!)
由于該零寬負(fù)向先行斷言沒(méi)有后綴表達(dá)式,試圖匹配總是失敗打洼。
每碰到一個(gè)左括號(hào)乾吻,就往堆棧內(nèi)壓入一個(gè)“Open”,每碰到一個(gè)右括號(hào)拟蜻,就彈出一個(gè)“Open”绎签,最后檢查堆棧是否為空,不為空則證明左括號(hào)多于右括號(hào)酝锅,匹配失敗诡必。正則表達(dá)式引擎進(jìn)行回溯(放棄最前面或最后面的一些字符),使整個(gè)表達(dá)式得到匹配搔扁。完整表達(dá)式如下:
< #最外層的左括號(hào)
[^<>]* #最外層的左括號(hào)后面的不是括號(hào)的內(nèi)容
(
(
(?'Open'<) #碰到了左括號(hào)爸舒,在黑板上寫(xiě)一個(gè)"Open"
[^<>]* #匹配左括號(hào)后面的不是括號(hào)的內(nèi)容
)+
(
(?'-Open'>) #碰到了右括號(hào),擦掉一個(gè)"Open"
[^<>]* #匹配右括號(hào)后面不是括號(hào)的內(nèi)容
)+
)*
(?(Open)(?!)) #在遇到最外層的右括號(hào)前面稿蹲,判斷黑板上還有沒(méi)有沒(méi)擦掉的"Open"扭勉;如果還有,則匹配失敗
>
平衡組的另一個(gè)常見(jiàn)應(yīng)用就是匹配HTML苛聘,如匹配嵌套的<div>標(biāo)簽涂炎。但是大多數(shù)系統(tǒng)(除Perl.NET\PCRE/PHP)中,正則表達(dá)式無(wú)法匹配任意深度的嵌套結(jié)構(gòu)设哗。