粗通正則表達(dá)式

前言

本文并非原創(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é)尾,<imgsrc=間有若干(大于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]ygr(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ì)匹配singdanc岩瘦。

零寬正回顧后發(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í)座柱,由于Iraqq結(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)设哗。

補(bǔ)充

正則表達(dá)式在線(xiàn)測(cè)試工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唱捣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子网梢,更是在濱河造成了極大的恐慌震缭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件战虏,死亡現(xiàn)場(chǎng)離奇詭異拣宰,居然都是意外死亡党涕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)巡社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)遣鼓,“玉大人,你說(shuō)我怎么就攤上這事重贺∑锼睿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵气笙,是天一觀的道長(zhǎng)次企。 經(jīng)常有香客問(wèn)我,道長(zhǎng)潜圃,這世上最難降的妖魔是什么缸棵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谭期,結(jié)果婚禮上堵第,老公的妹妹穿的比我還像新娘。我一直安慰自己隧出,他們只是感情好踏志,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著胀瞪,像睡著了一般针余。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凄诞,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天圆雁,我揣著相機(jī)與錄音,去河邊找鬼帆谍。 笑死伪朽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汛蝙。 我是一名探鬼主播烈涮,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼患雇!你這毒婦竟也來(lái)了跃脊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤苛吱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后器瘪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翠储,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绘雁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了援所。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庐舟。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖住拭,靈堂內(nèi)的尸體忽然破棺而出挪略,到底是詐尸還是另有隱情,我是刑警寧澤滔岳,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布杠娱,位于F島的核電站,受9級(jí)特大地震影響谱煤,放射性物質(zhì)發(fā)生泄漏摊求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一刘离、第九天 我趴在偏房一處隱蔽的房頂上張望室叉。 院中可真熱鬧,春花似錦硫惕、人聲如沸茧痕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凿渊。三九已至,卻和暖如春缚柳,著一層夾襖步出監(jiān)牢的瞬間埃脏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工秋忙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彩掐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓灰追,卻偏偏與公主長(zhǎng)得像堵幽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弹澎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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