正則表達式30分鐘入門教程【優(yōu)秀】

之前看過的關于正則的一篇優(yōu)秀文章,今兒上午沒事胶背,摘錄給大家
復習巷嚣、分享
放下微信,耐心讀完
相信我钳吟,你會有所收獲的

image.png

本文目標

30分鐘內(nèi)讓你明白正則表達式是什么廷粒,并對它有一些基本的了解,讓你可以在自己的程序或網(wǎng)頁里使用它。

如何使用本教程

最重要的是——請給我30分鐘坝茎,如果你沒有使用正則表達式的經(jīng)驗涤姊,請不要試圖在30秒內(nèi)入門——除非你是超人 :)

別被下面那些復雜的表達式嚇倒,只要跟著我一步一步來嗤放,你會發(fā)現(xiàn)正則表達式其實并沒有想像中的那么困難砂轻。當然,如果你看完了這篇教程之后斤吐,發(fā)現(xiàn)自己明白了很多搔涝,卻又幾乎什么都記不得,那也是很正常的——我認為和措,沒接觸過正則表達式的人在看完這篇教程后庄呈,能把提到過的語法記住80%以上的可能性為零。這里只是讓你明白基本的原理派阱,以后你還需要多練習诬留,多使用,才能熟練掌握正則表達式贫母。

除了作為入門教程之外文兑,本文還試圖成為可以在日常工作中使用的正則表達式語法參考手冊。就作者本人的經(jīng)歷來說腺劣,這個目標還是完成得不錯的——你看绿贞,我自己也沒能把所有的東西記下來,不是嗎橘原?

清除格式 文本格式約定:專業(yè)術語 元字符/語法格式 正則表達式 正則表達式中的一部分(用于分析) 對其進行匹配的源字符串 對正則表達式或其中一部分的說明
隱藏邊注 本文右邊有一些注釋籍铁,主要是用來提供一些相關信息,或者給沒有程序員背景的讀者解釋一些基本概念趾断,通尘苊可以忽略。

正則表達式到底是什么東西芋酌?

字符是計算機軟件處理文字時最基本的單位增显,可能是字母,數(shù)字脐帝,標點符號同云,空格,換行符腮恩,漢字等等梢杭。字符串是0個或更多個字符的序列。文本也就是文字秸滴,字符串。說某個字符串匹配某個正則表達式募判,通常是指這個字符串里有一部分(或幾部分分別)能滿足表達式給出的條件荡含。

在編寫處理字符串的程序或網(wǎng)頁時咒唆,經(jīng)常會有查找符合某些復雜規(guī)則的字符串的需要。正則表達式就是用于描述這些規(guī)則的工具释液。換句話說全释,【正則表達式就是記錄文本規(guī)則的代碼∥笳】

很可能你使用過Windows/Dos下用于文件查找的通配符(wildcard)浸船,也就是【* 和 ?。如果你想查找某個目錄下的所有的Word文檔的話寝蹈,你會搜索 * .doc】李命。在這里, 會被解釋成任意的字符串箫老》庾郑】*和通配符類似,正則表達式也是用來進行文本匹配的工具耍鬓,只不過比起通配符阔籽,它能更精確地描述你的需求——當然,代價就是更復雜——比如你可以編寫一個正則表達式牲蜀,用來查找所有以0開頭笆制,后面跟著2-3個數(shù)字,然后是一個連字號“-”,最后是7或8位數(shù)字的字符串(像010-12345678或0376-7654321)充蓝。

入門

學習正則表達式的最好方法是從例子開始褂策,理解例子之后再自己對例子進行修改,實驗开缎。下面給出了不少簡單的例子,并對它們作了詳細的說明林螃。

假設你在一篇英文小說里查找hi奕删,你可以使用正則表達式hi。

這幾乎是最簡單的正則表達式了疗认,它可以精確匹配這樣的字符串:由兩個字符組成完残,前一個字符是h,后一個是i。通常横漏,處理正則表達式的工具會提供一個忽略大小寫的選項谨设,如果選中了這個選項,它可以匹配hi,HI,Hi,hI這四種情況中的任意一種缎浇。

不幸的是扎拣,很多單詞里包含hi這兩個連續(xù)的字符,比如him,history,high等等。用hi來查找的話二蓝,這里邊的hi也會被找出來誉券。如果要精確地查找hi這個單詞的話,我們應該使用\bhi\b刊愚。

\b是正則表達式規(guī)定的一個特殊代碼(好吧踊跟,某些人叫它元字符,metacharacter)鸥诽,代表著單詞的開頭或結(jié)尾商玫,也就是單詞的分界處。雖然通常英文的單詞是由空格牡借,標點符號或者換行來分隔的拳昌,但是\b并不匹配這些單詞分隔字符中的任何一個,它只匹配一個位置蓖捶。

如果需要更精確的說法地回,\b匹配這樣的位置:它的前一個字符和后一個字符
不全是(一個是,一個不是或不存在)\w。

假如你要找的是hi后面不遠處跟著一個Lucy俊鱼,你應該用\bhi\b.\bLucy\b刻像。*

這里,.是另一個元字符并闲,匹配除了換行符以外的任意字符细睡。* 同樣是元字符,不過它代表的不是字符帝火,也不是位置溜徙,而是數(shù)量——它指定* 前邊的內(nèi)容可以連續(xù)重復使用任意次以使整個表達式得到匹配。因此犀填,.* 連在一起就意味著任意數(shù)量的不包含換行的字符〈酪迹現(xiàn)在\bhi\b.*\bLucy\b的意思就很明顯了:【先是一個單詞hi,然后是任意個任意字符(但不能是換行),最后是Lucy這個單詞九巡⊥济常】

換行符就是'\n',ASCII編碼為10(十六進制0x0A)的字符。
如果同時使用其它元字符冕广,我們就能構(gòu)造出功能更強大的正則表達式疏日。比如下面這個例子:

0\d\d-\d\d\d\d\d\d\d\d匹配這樣的字符串:以0開頭,然后是兩個數(shù)字撒汉,然后是一個連字號“-”沟优,最后是8個數(shù)字(也就是中國的電話號碼。當然睬辐,這個例子只能匹配區(qū)號為3位的情形)挠阁。

這里的\d是個新的元字符宾肺,匹配一位數(shù)字(0,或1鹃唯,或2爱榕,或……)瓣喊。
- 不是元字符坡慌,只匹配它本身——連字符(或者減號,或者中橫線藻三,或者隨你怎么稱呼它)洪橘。

為了避免那么多煩人的重復,我們也可以這樣寫這個表達式:0\d{2}-\d{8}棵帽。這里\d后面的{2}({8})的意思是前面\d必須連續(xù)重復匹配2次(8次)熄求。

測試正則表達式

如果你不覺得正則表達式很難讀寫的話,要么你是一個天才逗概,要么弟晚,你不是地球人。正則表達式的語法很令人頭疼逾苫,即使對經(jīng)常使用它的人來說也是如此卿城。由于難于讀寫,容易出錯铅搓,所以找一種工具對正則表達式進行測試是很有必要的瑟押。

不同的環(huán)境下正則表達式的一些細節(jié)是不相同的,本教程介紹的是微軟 .Net Framework 4.5 下正則表達式的行為星掰,所以多望,我向你推薦我編寫的.Net下的工具 Regester。請參考該頁面的說明來安裝和運行該軟件氢烘。

其實可以用在線的測試工具怀偷,VSCode也有對應的插件

下面是Regester運行時的截圖:

15350817627186.png

元字符

現(xiàn)在你已經(jīng)知道幾個很有用的元字符了,如\b,.,*播玖,還有\(zhòng)d.正則表達式里還有更多的元字符椎工,比如\s匹配任意的空白符,包括空格黎棠,制表符(Tab)晋渺,換行符,中文全角空格等脓斩。\w匹配字母或數(shù)字或下劃線或漢字等木西。

下面來看看更多的例子:


\ba\w*\b 匹配以字母a開頭的單詞——先是某個單詞開始處(\b),然后是字母a,然后是任意數(shù)量的字母或數(shù)字(\w*)随静,最后是單詞結(jié)束處(\b)八千。

\b\w{6}\b 匹配剛好6個字符的單詞吗讶。

注釋


好吧,現(xiàn)在我們說說正則表達式里的單詞是什么意思吧:就是不少于一個的連續(xù)的\w恋捆。
不錯照皆,這與學習英文時要背的成千上萬個同名的東西的確關系不大 :)
\d+匹配1個或更多連續(xù)的數(shù)字。這里的+是和*類似的元字符沸停,
不同的是*匹配重復任意次(可能是0次)膜毁,而+則匹配重復1次或更多次。

表1.常用的元字符

代碼 說明
. 匹配除換行符以外的任意字符
\w 匹配字母或數(shù)字或下劃線或漢字
\s 匹配任意的空白符
\d 匹配數(shù)字
\b 匹配單詞的開始或結(jié)束
^ 匹配字符串的開始
$ 匹配字符串的結(jié)束

注釋

正則表達式引擎通常會提供一個“測試指定的字符串是否匹配一個正則表達式”的方法愤钾,
如JavaScript里的RegExp.test()方法或.NET里的Regex.IsMatch()方法瘟滨。
這里的匹配是指是字符串里有沒有符合表達式規(guī)則的部分。如果不使用^和$的話能颁,
對于\d{5,12}而言杂瘸,使用這樣的方法就只能保證字符串里包含5到12連續(xù)位數(shù)字,
而不是整個字符串就是5到12位數(shù)字伙菊。

元字符 ^(和數(shù)字6在同一個鍵位上的符號)和$都匹配一個位置败玉,這和\b有點類似。^ 匹配你要用來查找的字符串的開頭镜硕,$匹配結(jié)尾运翼。這兩個代碼在驗證輸入的內(nèi)容時非常有用,比如一個網(wǎng)站如果要求你填寫的QQ號必須為5位到12位數(shù)字時谦疾,可以使用:^\d{5,12}$南蹂。

這里的{5,12}和前面介紹過的{2}是類似的,只不過{2}匹配只能不多不少重復2次念恍,{5,12}則是重復的次數(shù)不能少于5次六剥,不能多于12次,否則都不匹配峰伙。

因為使用了^和$疗疟,所以輸入的整個字符串都要用來和\d{5,12}來匹配,也就是說整個輸入必須是5到12個數(shù)字瞳氓,因此如果輸入的QQ號能匹配這個正則表達式的話策彤,那就符合要求了。

和忽略大小寫的選項類似匣摘,有些正則表達式處理工具還有一個處理多行的選項店诗。如果選中了這個選項,^和$的意義就變成了匹配行的開始處和結(jié)束處音榜。

字符轉(zhuǎn)義

如果你想查找元字符本身的話庞瘸,比如你查找. ,或者 ,就出現(xiàn)了問題:你沒辦法指定它們,因為它們會被解釋成別的意思赠叼。這時你就得使用\來取消這些字符的特殊意義擦囊。因此违霞,你應該使用 \\.和 \\ 。當然瞬场,要查找\本身买鸽,你也得用 \\ .

例如:deerchao\.net匹配deerchao.net,C:\\Windows匹配C:\Windows贯被。

重復

你已經(jīng)看過了前面的*, +, {2}, {5,12} 這幾個匹配重復的方式了眼五。下面是正則表達式中所有的限定符(指定數(shù)量的代碼,例如*,{5,12}等):

表2.常用的限定符

代碼/語法 說明
* 重復零次或更多次
+ 重復一次或更多次
? 重復零次或一次
{n} 重復n次
{n,} 重復n次或更多次
{n,m} 重復n到m次

下面是一些使用重復的例子:

Windows\d+匹配Windows后面跟1個或更多數(shù)字

^\w+匹配一行的第一個單詞(或整個字符串的第一個單詞刃榨,具體匹配哪個意思得看選項設置)

字符類

要想查找數(shù)字弹砚,字母或數(shù)字双仍,空白是很簡單的枢希,因為已經(jīng)有了對應這些字符集合的元字符,但是如果你想匹配沒有預定義元字符的字符集合(比如元音字母a,e,i,o,u),應該怎么辦朱沃?

很簡單苞轿,你只需要在方括號里列出它們就行了,像[aeiou]就匹配任何一個英文元音字母逗物,[.?!]匹配標點符號(.或?或!)搬卒。

我們也可以輕松地指定一個字符范圍,像[0-9]代表的含意與\d就是完全一致的:一位數(shù)字翎卓;同理[a-z0-9A-Z_]也完全等同于\w(如果只考慮英文的話)契邀。

下面是一個更復雜的表達式:(?0\d{2}[) -]?\d{8}。

“(”和“)”也是元字符失暴,后面的分組節(jié)里會提到坯门,所以在這里需要使用轉(zhuǎn)義。
這個表達式可以匹配幾種格式的電話號碼逗扒,像(010)88886666古戴,或022-22334455,或02912345678等矩肩。我們對它進行一些分析吧:首先是一個轉(zhuǎn)義字符\(它能出現(xiàn)0次或1次(?),然后是一個0现恼,后面跟著2個數(shù)字(\d{2}),然后是)或-或空格中的一個黍檩,它出現(xiàn)1次或不出現(xiàn)(?)叉袍,最后是8個數(shù)字(\d{8})。

分枝條件

不幸的是刽酱,剛才那個表達式也能匹配010)12345678或(022-87654321這樣的“不正確”的格式喳逛。要解決這個問題,我們需要用到分枝條件肛跌。正則表達式里的分枝條件指的是有幾種規(guī)則艺配,如果滿足其中任意一種規(guī)則都應該當成匹配察郁,具體方法是用|把不同的規(guī)則分隔開。聽不明白转唉?沒關系皮钠,看例子:


0\d{2}-\d{8}|0\d{3}-\d{7}這個表達式能匹配兩種以連字號分隔的電話號碼:一種是三位區(qū)號,8位本地號(如010-12345678)赠法,
一種是4位區(qū)號麦轰,7位本地號(0376-2233445)。

\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}這個表達式匹配3位區(qū)號的電話號碼砖织,其中區(qū)號可以用小括號括起來款侵,也可以不用,
區(qū)號與本地號間可以用連字號或空格間隔侧纯,也可以沒有間隔新锈。你可以試試用分枝條件把這個表達式擴展成也支持4位區(qū)號的。

\d{5}-\d{4}|\d{5}這個表達式用于匹配美國的郵政編碼眶熬。美國郵編的規(guī)則是5位數(shù)字妹笆,或者用連字號間隔的9位數(shù)字。
之所以要給出這個例子是因為它能說明一個問題:使用分枝條件時娜氏,要注意各個條件的順序拳缠。如果你把它改成\d{5}|\d{5}-\d{4}的話,
那么就只會匹配5位的郵編(以及9位郵編的前5位)贸弥。
原因是匹配分枝條件時窟坐,將會從左到右地測試每個條件,如果滿足了某個分枝的話绵疲,就不會去再管其它的條件了哲鸳。

分組

我們已經(jīng)提到了怎么重復單個字符(直接在字符后面加上限定符就行了);但如果想要重復多個字符又該怎么辦最岗?你可以用小括號來指定子表達式(也叫做分組)帕胆,然后你就可以指定這個子表達式的重復次數(shù)了,你也可以對子表達式進行其它一些操作(后面會有介紹)般渡。

(\d{1,3}.){3}\d{1,3}是一個簡單的IP地址匹配表達式懒豹。要理解這個表達式,請按下列順序分析它:\d{1,3}匹配1到3位的數(shù)字驯用,(\d{1,3}.){3}匹配三位數(shù)字加上一個英文句號(這個整體也就是這個分組)重復3次脸秽,最后再加上一個一到三位的數(shù)字(\d{1,3})。

IP地址中每個數(shù)字都不能大于255. 經(jīng)常有人問我, 01.02.03.04 這樣前面帶有0的數(shù)字, 是不是正確的IP地址呢? 答案是: 是的, IP 地址里的數(shù)字可以包含有前導 0 (leading zeroes).

不幸的是蝴乔,它也將匹配256.300.888.999這種不可能存在的IP地址记餐。如果能使用算術比較的話,或許能簡單地解決這個問題薇正,但是正則表達式中并不提供關于數(shù)學的任何功能片酝,所以只能使用冗長的分組囚衔,選擇,字符類來描述一個正確的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)雕沿。

理解這個表達式的關鍵是理解2[0-4]\d|25[0-5]|[01]?\d\d?练湿,這里我就不細說了,你自己應該能分析得出來它的意義审轮。

反義

有時需要查找不屬于某個能簡單定義的字符類的字符肥哎。比如想查找除了數(shù)字以外,其它任意字符都行的情況疾渣,這時需要用到反義:

表3.常用的反義代碼

代碼/語法 說明
\W 匹配任意不是字母篡诽,數(shù)字,下劃線榴捡,漢字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非數(shù)字的字符
\B 匹配不是單詞開頭或結(jié)束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou這幾個字母以外的任意字符

例子:

\S+匹配不包含空白符的字符串杈女。

<a[^>]+>匹配用尖括號括起來的以a開頭的字符串。
呃……其實,組號分配還不像我剛說得那么簡單:

分組0對應整個正則表達式
實際上組號分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配薄疚,第二遍只給命名組分配--因此所有命名組的組號都大于未命名的組號
你可以使用(?:exp)這樣的語法來剝奪一個分組對組號分配的參與權.

后向引用

使用小括號指定一個子表達式后碧信,匹配這個子表達式的文本(也就是此分組捕獲的內(nèi)容)可以在表達式或其它程序中作進一步的處理。默認情況下街夭,每個分組會自動擁有一個組號,規(guī)則是:從左向右躏筏,以分組的左括號為標志板丽,第一個出現(xiàn)的分組的組號為1,第二個為2趁尼,以此類推埃碱。

后向引用用于重復搜索前面某個分組匹配的文本。例如酥泞,\1代表分組1匹配的文本砚殿。難以理解?

請看示例:

\b(\w+)\b\s+\1\b可以用來匹配重復的單詞芝囤,像go go, 或者kitty kitty似炎。這個表達式首先是一個單詞,也就是單詞開始處和結(jié)束處之間的多于一個的字母或數(shù)字(\b(\w+)\b)悯姊,這個單詞會被捕獲到編號為1的分組中羡藐,然后是1個或幾個空白符(\s+),最后是分組1中捕獲的內(nèi)容(也就是前面匹配的那個單詞)(\1)悯许。

你也可以自己指定子表達式的組名仆嗦。要指定一個子表達式的組名,請使用這樣的語法:(?<Word>\w+)(或者把尖括號換成'也行:(?'Word'\w+)),這樣就把\w+的組名指定為Word了先壕。要反向引用這個分組捕獲的內(nèi)容瘩扼,你可以使用\k<Word>,所以上一個例子也可以寫成這樣:\b(?<Word>\w+)\b\s+\k<Word>\b谆甜。

使用小括號的時候,還有很多特定用途的語法集绰。下面列出了最常用的一些:

表4.常用分組語法

分類 代碼/語法 說明
捕獲 (exp) 匹配exp,并捕獲文本到自動命名的組里
(?<name>exp) 匹配exp,并捕獲文本到名稱為name的組里店印,也可以寫成 (?'name'exp)
(?:exp) 匹配exp,不捕獲匹配的文本,也不給此分組分配組號
零寬斷言 (?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置
注釋 (?#comment) 這種類型的分組不對正則表達式的處理產(chǎn)生任何影響倒慧,用于提供注釋讓人閱讀

我們已經(jīng)討論了前兩種語法按摘。第三個(?:exp)不會改變正則表達式的處理方式,只是這樣的組匹配的內(nèi)容不會像前兩種那樣被捕獲到某個組里面纫谅,也不會擁有組號炫贤。“我為什么會想要這樣做付秕?”——好問題兰珍,你覺得為什么呢?

零寬斷言

地球人询吴,是不是覺得這些術語名稱太復雜掠河,太難記了?我也有同感猛计。知道有這么一種東西就行了唠摹,它叫什么,隨它去吧奉瘤!人若無名勾拉,便可專心練劍;物若無名盗温,便可隨意取舍……
斷言用來聲明一個應該為真的事實藕赞。正則表達式中只有當斷言為真時才會繼續(xù)進行匹配。

接下來的四個用于查找在某些內(nèi)容(但并不包括這些內(nèi)容)之前或之后的東西卖局,也就是說它們像\b, ^, $那樣用于指定一個位置斧蜕,這個位置應該滿足一定的條件(即斷言),因此它們也被稱為零寬斷言砚偶。最好還是拿例子來說明吧:

(?=exp)也叫零寬度正預測先行斷言批销,它斷言自身出現(xiàn)的位置的后面能匹配表達式exp。比如\b\w+(?=ing\b)蟹演,匹配以ing結(jié)尾的單詞的前面部分(除了ing以外的部分)风钻,如查找I'm singing while you're dancing.時,它會匹配sing和danc酒请。

(?<=exp)也叫零寬度正回顧后發(fā)斷言骡技,它斷言自身出現(xiàn)的位置的前面能匹配表達式exp。比如(?<=\bre)\w+\b會匹配以re開頭的單詞的后半部分(除了re以外的部分),例如在查找reading a book時布朦,它匹配ading囤萤。

假如你想要給一個很長的數(shù)字中每三位間加一個逗號(當然是從右邊加起了),你可以這樣查找需要在前面和里面添加逗號的部分:((?<=\d)\d{3})+\b是趴,用它對1234567890進行查找時結(jié)果是234567890涛舍。

下面這個例子同時使用了這兩種斷言:(?<=\s)\d+(?=\s)匹配以空白符間隔的數(shù)字(再次強調(diào),不包括這些空白符)唆途。

負向零寬斷言

前面我們提到過怎么查找不是某個字符或不在某個字符類里的字符的方法(反義)富雅。但是如果我們只是想要確保某個字符沒有出現(xiàn),但并不想去匹配它時怎么辦肛搬?

例如没佑,如果我們想查找這樣的單詞--它里面出現(xiàn)了字母q,但是q后面跟的不是字母u,我們可以嘗試這樣:

\b\w*q[^u]\w*\b 匹配包含后面不是字母u的字母q的單詞。但是如果多做測試(或者你思維足夠敏銳温赔,直接就觀察出來了)蛤奢,你會發(fā)現(xiàn),如果q出現(xiàn)在單詞的結(jié)尾的話陶贼,像Iraq,Benq啤贩,這個表達式就會出錯。這是因為[^u]總要匹配一個字符拜秧,所以如果q是單詞的最后一個字符的話痹屹,后面的[^u]將會匹配q后面的單詞分隔符(可能是空格,或者是句號或其它的什么)腹纳,后面的\w*\b將會匹配下一個單詞痢掠,于是\b\w*q[^u]\w*\b就能匹配整個Iraq fighting。負向零寬斷言能解決這樣的問題嘲恍,因為它只匹配一個位置,并不消費任何字符⌒劬裕現(xiàn)在佃牛,我們可以這樣來解決這個問題:\b\w*q(?!u)\w*\b。

零寬度負預測先行斷言(?!exp)医舆,斷言此位置的后面不能匹配表達式exp俘侠。例如:\d{3}(?!\d)匹配三位數(shù)字,而且這三位數(shù)字的后面不能是數(shù)字蔬将;\b((?!abc)\w)+\b匹配不包含連續(xù)字符串a(chǎn)bc的單詞爷速。

同理,我們可以用(?<!exp),零寬度負回顧后發(fā)斷言來斷言此位置的前面不能匹配表達式exp:(?<![a-z])\d{7}匹配前面不是小寫字母的七位數(shù)字霞怀。

請詳細分析表達式(?<=<(\w+)>).\*(?=<\/\1>)惫东,這個表達式最能表現(xiàn)零寬斷言的真正用途。
一個更復雜的例子:(?<=<(\w+)>).\*(?=<\/\1>)匹配不包含屬性的簡單HTML標簽內(nèi)里的內(nèi)容。(?<=<(\w+)>)指定了這樣的前綴:
被尖括號括起來的單詞(比如可能是<b>)廉沮,然后是.*(任意的字符串),最后是一個后綴(?=<\/\1>)颓遏。注意后綴里的\/,它用到了前面
提過的字符轉(zhuǎn)義滞时;\1則是一個反向引用叁幢,引用的正是捕獲的第一組,前面的(\w+)匹配的內(nèi)容坪稽,這樣如果前綴實際上是<b>的話曼玩,
后綴就是</b>了。整個表達式匹配的是<b>和</b>之間的內(nèi)容(再次提醒窒百,不包括前綴和后綴本身)黍判。

注釋

小括號的另一種用途是通過語法(?#comment)來包含注釋。例如:2[0-4]\d(?#200-249)|250-5|[01]?\d\d?(?#0-199)贝咙。

要包含注釋的話样悟,最好是啟用“忽略模式里的空白符”選項,這樣在編寫表達式時能任意的添加空格庭猩,Tab窟她,換行,而實際使用時這些都將被忽略蔼水。啟用這個選項后震糖,在#后面到這一行結(jié)束的所有文本都將被當成注釋忽略掉。例如趴腋,我們可以前面的一個表達式寫成這樣:

  (?<=    # 斷言要匹配的文本的前綴
  <(\w+)> # 查找尖括號括起來的字母或數(shù)字(即HTML/XML標簽)
  )       # 前綴結(jié)束
  .*      # 匹配任意文本
  (?=     # 斷言要匹配的文本的后綴
  <\/\1>  # 查找尖括號括起來的內(nèi)容:前面是一個"/"吊说,后面是先前捕獲的標簽
  )       # 后綴結(jié)束

貪婪與懶惰

當正則表達式中包含能接受重復的限定符時,通常的行為是(在使整個表達式能得到匹配的前提下)匹配盡可能多的字符优炬。以這個表達式為例:a.*b颁井,它將會匹配最長的以a開始,以b結(jié)束的字符串蠢护。如果用它來搜索aabab的話雅宾,它會匹配整個字符串a(chǎn)abab。這被稱為貪婪匹配葵硕。

有時眉抬,我們更需要懶惰匹配,也就是匹配盡可能少的字符懈凹。前面給出的限定符都可以被轉(zhuǎn)化為懶惰匹配模式蜀变,只要在它后面加上一個問號?。這樣.*?就意味著匹配任意數(shù)量的重復介评,但是在能使整個匹配成功的前提下使用最少的重復】獗保現(xiàn)在看看懶惰版的例子吧:

a.?b匹配最短的,以a開始,以b結(jié)束的字符串贤惯。如果把它應用于aabab的話洼专,它會匹配aab(第一到第三個字符)和ab(第四到第五個字符)。*

為什么第一個匹配是aab(第一到第三個字符)而不是ab(第二到第三個字符)孵构?簡單地說屁商,因為正則表達式有另一條規(guī)則,比懶惰/貪婪規(guī)則的優(yōu)先級更高:最先開始的匹配擁有最高的優(yōu)先權——The match that begins earliest wins颈墅。
表5.懶惰限定符
代碼/語法 說明

代碼/語法 說明

| *? | 重復任意次蜡镶,但盡可能少重復
| +? | 重復1次或更多次,但盡可能少重復
| ?? | 重復0次或1次恤筛,但盡可能少重復
| {n,m}?| 重復n到m次官还,但盡可能少重復
| {n,}?| 重復n次以上,但盡可能少重復

處理選項

在C#中毒坛,你可以使用Regex(String, RegexOptions)構(gòu)造函數(shù)來設置正則表達式的處理選項望伦。如:Regex regex = new Regex(@"\ba\w{6}\b", RegexOptions.IgnoreCase);
上面介紹了幾個選項如忽略大小寫,處理多行等煎殷,這些選項能用來改變處理正則表達式的方式屯伞。下面是.Net中常用的正則表達式選項:

表6.常用的處理選項

名稱 說明
IgnoreCase(忽略大小寫) 匹配時不區(qū)分大小寫。
Multiline(多行模式) 更改^和$的含義豪直,使它們分別在任意一行的行首和行尾匹配劣摇,而不僅僅在整個字符串的開頭和結(jié)尾匹配。(在此模式下,$的精確含意是:匹配\n之前的位置以及字符串結(jié)束前的位置.)
Singleline(單行模式) 更改.的含義弓乙,使它與每一個字符匹配(包括換行符\n)末融。
IgnorePatternWhitespace(忽略空白) 忽略表達式中的非轉(zhuǎn)義空白并啟用由#標記的注釋。
ExplicitCapture(顯式捕獲) 僅捕獲已被顯式命名的組暇韧。

一個經(jīng)常被問到的問題是:是不是只能同時使用多行模式和單行模式中的一種勾习?答案是:不是。這兩個選項之間沒有任何關系懈玻,除了它們的名字比較相似(以至于讓人感到疑惑)以外语卤。

平衡組/遞歸匹配

這里介紹的平衡組語法是由.Net Framework支持的;其它語言/庫不一定支持這種功能酪刀,或者支持此功能但需要使用不同的語法。

有時我們需要匹配像( 100 * ( 50 + 15 ) )這樣的可嵌套的層次性結(jié)構(gòu)钮孵,這時簡單地使用(.+)則只會匹配到最左邊的左括號和最右邊的右括號之間的內(nèi)容(這里我們討論的是貪婪模式骂倘,懶惰模式也有下面的問題)。假如原來的字符串里的左括號和右括號出現(xiàn)的次數(shù)不相等巴席,比如( 5 / ( 3 + 2 ) ) )历涝,那我們的匹配結(jié)果里兩者的個數(shù)也不會相等。有沒有辦法在這樣的字符串里匹配到最長的,配對的括號之間的內(nèi)容呢荧库?

為了避免(和(把你的大腦徹底搞糊涂堰塌,我們還是用尖括號代替圓括號吧。現(xiàn)在我們的問題變成了如何把xx <aa <bbb> <bbb> aa> yy這樣的字符串里分衫,最長的配對的尖括號內(nèi)的內(nèi)容捕獲出來场刑?

這里需要用到以下的語法構(gòu)造:

(?'group') 把捕獲的內(nèi)容命名為group,并壓入堆棧(Stack)
(?'-group') 從堆棧上彈出最后壓入堆棧的名為group的捕獲內(nèi)容,如果堆棧本來為空蚪战,則本分組的匹配失敗
(?(group)yes|no) 如果堆棧上存在以名為group的捕獲內(nèi)容的話牵现,繼續(xù)匹配yes部分的表達式,否則繼續(xù)匹配no部分
(?!) 零寬負向先行斷言邀桑,由于沒有后綴表達式瞎疼,試圖匹配總是失敗

如果你不是一個程序員(或者你自稱程序員但是不知道堆棧是什么東西),你就這樣理解上面的三種語法吧:第一個就是在黑板上寫一個"group"壁畸,第二個就是從黑板上擦掉一個"group"贼急,第三個就是看黑板上寫的還有沒有"group",如果有就繼續(xù)匹配yes部分捏萍,否則就匹配no部分太抓。
我們需要做的是每碰到了左括號,就在壓入一個"Open",每碰到一個右括號照弥,就彈出一個腻异,到了最后就看看堆棧是否為空--如果不為空那就證明左括號比右括號多,那匹配就應該失敗这揣。正則表達式引擎會進行回溯(放棄最前面或最后面的一些字符)悔常,盡量使整個表達式得到匹配。

<                         #最外層的左括號
    [^<>]*                #最外層的左括號后面的不是括號的內(nèi)容
    (
        (
            (?'Open'<)    #碰到了左括號给赞,在黑板上寫一個"Open"
            [^<>]*       #匹配左括號后面的不是括號的內(nèi)容
        )+
        (
            (?'-Open'>)   #碰到了右括號机打,擦掉一個"Open"
            [^<>]*        #匹配右括號后面不是括號的內(nèi)容
        )+
    )*
    (?(Open)(?!))         #在遇到最外層的右括號前面呛哟,判斷黑板上還有沒有沒擦掉的"Open"提揍;如果還有斑匪,則匹配失敗

>                         #最外層的右括號

平衡組的一個最常見的應用就是匹配HTML,下面這個例子可以匹配嵌套的<div>標簽:

<div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>.

還有些什么東西沒提到

上邊已經(jīng)描述了構(gòu)造正則表達式的大量元素自娩,但是還有很多沒有提到的東西菊碟。下面是一些未提到的元素的列表苟耻,包含語法和簡單的說明棋弥。你可以在網(wǎng)上找到更詳細的參考資料來學習它們--當你需要用到它們的時候杖爽。如果你安裝了MSDN Library,你也可以在里面找到.net下正則表達式詳細的文檔耻台。這里的介紹很簡略空免,如果你需要更詳細的信息,而又沒有在電腦上安裝MSDN Library,可以查看關于正則表達式語言元素的MSDN在線文檔盆耽。

表7.尚未詳細討論的語法

代碼/語法 說明
\a 報警字符(打印它的效果是電腦嘀一聲)
\b 通常是單詞分界位置蹋砚,但如果在字符類里使用代表退格
\t 制表符扼菠,Tab
\r 回車
\v 豎向制表符
\f 換頁符
\n 換行符
\e Escape
\0nn ASCII代碼中八進制代碼為nn的字符
\xnn ASCII代碼中十六進制代碼為nn的字符
\unnnn Unicode代碼中十六進制代碼為nnnn的字符
\cN ASCII控制字符。比如\cC代表Ctrl+C
\A 字符串開頭(類似^坝咐,但不受處理多行選項的影響)
\Z 字符串結(jié)尾或行尾(不受處理多行選項的影響)
\z 字符串結(jié)尾(類似$循榆,但不受處理多行選項的影響)
\G 當前搜索的開頭
\p{name} Unicode中命名為name的字符類,例如\p{IsGreek}
(?>exp) 貪婪子表達式
(?<x>-<y>exp) 平衡組
(?im-nsx:exp) 在子表達式exp中改變處理選項
(?im-nsx) 為表達式后面的部分改變處理選項
(?(exp)yes no) 把exp當作零寬正向先行斷言墨坚,如果在這個位置能匹配秧饮,使用yes作為此組的表達式;否則使用no
(?(exp)yes) 同上框杜,只是使用空表達式作為no
(?(name)yes no) 如果命名為name的組捕獲到了內(nèi)容浦楣,使用yes作為表達式;否則使用no
(?(name)yes) 同上咪辱,只是使用空表達式作為no

聯(lián)系作者

好吧,我承認,我騙了你,讀到這里你肯定花了不止30分鐘.相信我,這是我的錯,而不是因為你太笨.我之所以說"30分鐘",是為了讓你有信心,有耐心繼續(xù)下去.既然你看到了這里,那證明我的陰謀成功了.被忽悠的感覺很爽吧振劳?

要投訴我,或者覺得我其實可以忽悠得更高明,歡迎來我的微博讓我知道. 如果你有關于正則表達式的問題, 可以到 stackoverflow 網(wǎng)站上提問, 記得要添加 regex 標簽. 如果你更習慣于用中文交流, 可以到微博上用 #正則# 標簽提出問題.

原文鏈接

參考與相關鏈接

測試正則表達式
其它可用的測試工具
精通正則表達式(第3版)
微軟的正則表達式教程
System.Text.RegularExpressions.Regex類(MSDN)
專業(yè)的正則表達式教學網(wǎng)站(英文)
關于.Net下的平衡組的詳細討論(英文)

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市油狂,隨后出現(xiàn)的幾起案子历恐,更是在濱河造成了極大的恐慌,老刑警劉巖专筷,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱贼,死亡現(xiàn)場離奇詭異,居然都是意外死亡磷蛹,警方通過查閱死者的電腦和手機吮旅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來味咳,“玉大人庇勃,你說我怎么就攤上這事〔凼唬” “怎么了责嚷?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掂铐。 經(jīng)常有香客問我罕拂,道長,這世上最難降的妖魔是什么全陨? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任爆班,我火速辦了婚禮,結(jié)果婚禮上辱姨,老公的妹妹穿的比我還像新娘蛋济。我一直安慰自己,他們只是感情好炮叶,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布碗旅。 她就那樣靜靜地躺著,像睡著了一般镜悉。 火紅的嫁衣襯著肌膚如雪祟辟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天侣肄,我揣著相機與錄音旧困,去河邊找鬼。 笑死稼锅,一個胖子當著我的面吹牛吼具,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矩距,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼拗盒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锥债?” 一聲冷哼從身側(cè)響起陡蝇,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哮肚,沒想到半個月后登夫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡允趟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年恼策,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潮剪。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡涣楷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲁纠,到底是詐尸還是另有隱情总棵,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布改含,位于F島的核電站情龄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捍壤。R本人自食惡果不足惜骤视,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹃觉。 院中可真熱鬧专酗,春花似錦、人聲如沸盗扇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佑笋,卻和暖如春翼闹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒋纬。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工猎荠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜀备。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓关摇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親碾阁。 傳聞我的和親對象是個殘疾皇子输虱,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

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