正則(高級)(轉)

深刻理解正則表達式高級教程 Featured

做者:  Zjmainstay

本文是一篇正則表達式高級教程,主要經過對正則表達式幾個概念的介紹,深刻探討正則表達式高級功能,以期達到通俗化解釋正則表達式高深概念的目的。javascript

 

 

前面已經寫過一篇文章《我眼裏的正則表達式(入門)》介紹過正則表達式的基礎和基本套路正則三段論:定錨點,去噪點,取數據了,接下來這篇文章,補充一點相對高級的概念:php

1. 概念一:按單字符匹配
    2. 概念二:匹配優先和不匹配優先
    3. 概念三:貪婪模式與非貪婪模式
    4. 概念四:環視(斷言)
    5. 概念五:平衡組
    6. 概念六:模式修飾符
    7. 附:正則三段論應用
 

概念一:按單字符匹配

正則裏面的數據都是按照單個字符來進行匹配的,這個經過數值區間的例子最容易體現出來,好比,示例一:java

我要匹配0-15的數值區間,用正則來寫的話,即是[0-9]|1[0-5],這裏,即是把0-9這種單字符的狀況,和10-15這種多字符的狀況拆分開了,使用分支|來區分開,表示要麼是0-9,要麼是10-15。 
上面是兩位數值的狀況,如今延伸至1-65535,我我的的處理思想是從大到小,一塊塊分解:正則表達式

 
  1. 1. 65530-65535 ==> 6553[0-5] 末位區間0-5
  2. 2. 65500-65529 ==> 655[0-2][0-9] 第四位區間0-2,末位區間0-9
  3. 3. 65000-65499 ==> 65[0-4][0-9]{2} 第三位區間0-4,後兩位0-9
  4. 4. 60000-64999 ==> 6[0-4][0-9]{3} 第二位區間0-4,後三位0-9
  5. 5. 10000-59999 ==> [1-5][0-9]{4} 第一位區間1-5,後四位0-9
  6. 6. 1-9999 ==> [1-9][0-9]{0,3} 第一位只能是1-9,後三位無關緊要

最後組合起來: 
(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}) 
便獲得1-65535匹配正則。數據結構

根據數據處理需求,能夠在正則先後加上^$,以匹配整個數據串,或者先後加入\b,把它當作單詞邊界處理。沒有限定字符的邊界每每是js正則判斷中常見的錯誤之一。ide

 

概念二:匹配優先和不匹配優先

匹配優先和不匹配優先從字面理解也是比較容易的,所謂匹配優先,就是,能匹配我就先匹配;不匹配優先就是,能不匹配我就先不匹配,這段匹配先跳過,先看看後面的匹配能不能經過。工具

 

概念三:貪婪模式與非貪婪模式

正則的貪婪模式和非貪婪模式是一個比較容易混淆的概念,不過,咱們能夠這麼理解,一我的很貪婪,因此他會能拿多少拿多少,換過來,那就是貪婪模式下的正則表達式,能匹配多少就匹配多少,儘量最多。而非貪婪模式,則是能不匹配就不匹配,儘量最少。性能

下面舉個例子,示例二:學習

需求:匹配1後面跟任意個0
源串:10001
使用貪婪模式:10*       結果:1000 和 1
使用非貪婪模式:10*?    結果:1 和 1

首先,*是匹配0個或多個的意思。測試

 
  1. 貪婪模式下,它表示,首先匹配一個1,而後匹配1後面的0,最多能夠匹配30,所以獲得1000,而後第二次又匹配到一個1,可是後面沒有0,所以獲得1
 
  1. 非貪婪模式下,它表示,首先匹配一個1,而後1後面的0,能不匹配就不匹配了,因此,它每次都只是匹配了一個1

看到這裏,也許,有些人以爲,哎呀,我懂了! 
那麼,下來咱們改改,看看你是否是真懂了。

示例三:

需求:匹配1後面跟任意個0,再跟一個1
源串:10001
使用貪婪模式:10*1       結果:10001
使用非貪婪模式:10*?1    結果:10001

爲何這兩次的結果同樣了?

由於,正則表達式要判斷完這整個正則纔算成功,這種狀況下,

 
  1. 貪婪模式,首先匹配一個1,而後匹配1後面儘量多的0,發現3個,再匹配0後面的一個1,正則表達式匹配完,完成匹配,獲得10001
 
  1. 非貪婪模式,首先匹配一個1,而後,0*?是非貪婪模式,它不想匹配了(非貪婪模式不匹配優先),看看後面是否是1了?而後發現哎媽呀,後面是個0啊,而後它回頭,不能再偷懶了,用0*?匹配一個0吧,而後匹配到10,又不想匹配了,看看後面有沒有1了,仍是沒有,又回去用0*?匹配掉一個0,獲得100,繼續偷懶,可是發現後面還不是1,而後又用0*?匹配獲得1000,最後,發現真不容易啊,終於看到1了,匹配獲得10001,正則表達式匹配完,完成匹配。

看到這裏,是否是懂了? 
那究竟哪一個模式好呢?

何時使用貪婪模式,何時使用非貪婪模式,哪一個性能好,哪一個性能很差,不能一律而論,要根據狀況分析。 
下面我舉個例子: 
源碼:

 
  1. <a href="http://www.zjmainstay.cn/my-regexp" target="_blank" title="我眼裏的正則表達式(入門)">我眼裏的正則表達式(入門)</a>
  2. <a title="我眼裏的正則表達式(入門)" href="http://www.zjmainstay.cn/my-regexp" target="_blank">我眼裏的正則表達式(入門)</a>

正則1:<a [^>]*?href="([^"]*?)"[^>]*?>([^<]*?)</a>(238次) 
正則2:<a [^>]*?href="([^"]*)"[^>]*>([^<]*)</a>(65次) 
正則3:<a [^>]*href="([^"]*)"[^>]*>([^<]*)</a>(136次) 
附:執行次數的獲取請下載正則表達式測試工具:RegexBuddy 4.1.0-正則測試工具.rar,使用裏面的Debug功能。

正則1是通用寫法,正則2是在肯定字符不會溢出的狀況下消除非貪婪模式,正則3是證實並非所有消除非貪婪模式就是最優。

所以,關於貪婪模式好仍是非貪婪模式好的討論,只能說根據需求而定,不過,在平時的時候用,通常使用非貪婪模式較多,由於貪婪模式常常會因爲元字符範圍限制不嚴謹而致使匹配越界,獲得非預期結果。

在肯定的數據結構裏,能夠嘗試使用[^>]*>這樣的排除字符貪婪模式替換非貪婪模式,提高匹配的效率。注意,貪婪部分([^>]*)的匹配,最好不要越過其後面的字符(>),不然會致使貪婪模式下的回溯,如正則3,[^>]*的匹配越過了href,一直匹配到>爲止,而這時候再匹配href,會匹配不到而致使屢次回溯處理,直到回溯到href前的位置,後面才繼續了下去。

另外,須要注意一點,不管使用貪婪模式仍是非貪婪模式,在不一樣語言須要注意回溯次數和嵌套次數的限制,好比在PHP中,pcre.backtrack_limit=100000pcre.recursion_limit=100000

 

概念四:環視(斷言/零寬斷言)

環視,在不一樣的地方又稱之爲零寬斷言,簡稱斷言。 
用一句通俗的話解釋: 
環視,就是先從全局環顧一遍正則,(而後判定結果,)再作進一步匹配處理。 
斷言,就是先從全局環顧一遍正則,而後判定結果,再作進一步匹配處理。

兩個雖然字面不同,意思倒是同一個,都是作全局觀望,再作進一步處理。

環視的做用至關於對其所在位置加了一個附加條件,只有知足這個條件,環視子表達式才能匹配成功。

環視主要有如下4個用法: 
(?<=exp) 匹配前面是exp的數據 
(?<!exp) 匹配前面不是exp的數據 
(?=exp) 匹配後面是exp的數據 
(?!exp) 匹配後面不是exp的數據

示例四: 
(?<=B)AAA 匹配前面是B的數據,即BAAA匹配,而CAAA不匹配 
(?<!B)AAA 匹配前面不是B的數據,即CAAA匹配,而BAAA不匹配 
AAA(?=B) 匹配後面是B的數據,即AAAB匹配,而AAAC不匹配 
AAA(?!B) 匹配後面不是B的數據,即AAAC能匹配,而AAAB不能匹配

另外,還會看到(?!B)[A-Z]這種寫法,其實它是[A-Z]範圍裏,排除B的意思,前置的(?!B)只是對後面數據的一個限定,從而達到過濾匹配的效果。

所以,環視作排除處理是比較實用的,好比,示例五:

需求:字母、數字組合,不區分大小寫,不能純數字或者純字母,6-16個字符。
通用正則:^[a-z0-9]{6,16}$    字母數字組合,6-16個字符
    排除純字母:(?!^[a-z]+$)
排除純數字:(?!^[0-9]+$)
    組合起來:(?!^[a-z]+$)(?!^[0-9]+$)^[a-z0-9]{6,16}$

注意,環視部分是不佔寬度的,因此有零寬斷言的叫法。 
所謂不佔寬度,能夠分紅兩部分理解: 
一、環視的匹配結果不歸入數據結果 
二、環視它匹配過的地方,下次還能用它繼續匹配。

若是不是環視,則匹配過的地方,不能再匹配第二次了。

上面示例四體現了:環視的匹配結果不歸入數據結果,它的結果:

 
  1. (?<=B)AAA 源串:BAAA 結果:AAA
  2. (?<!B)AAA 源串:CAAA 結果:AAA
  3. AAA(?=B) 源串:AAAB 結果:AAA
  4. AAA(?!B) 源串:AAAC 結果:AAA

而示例五體現了:環視它匹配過的地方,下次還能用它繼續匹配 
由於,整個匹配過程當中,正則表達式一共走了3次字符串匹配,第一次匹配不所有是字母,第二次匹配不所有是數字,第三次匹配所有是字母數字組合,6-16個字符。

 
  1. 擴展部分:
  2. `[A-Z](?<=B)` [A-Z]範圍等於B
  3. `[A-Z](?<!B)` [A-Z]範圍排除B
  4. `(?!B)[A-Z]` [A-Z]範圍排除B

附: js不支持(?<=exp) 和 (?<!exp) 語法

 

概念五:平衡組

平衡組並非全部程序語言都支持,而我本人使用的PHP語言就不支持,因此平時接觸也是比較少的。

平衡組主要用到下面四個語法:

 
  1. (?'group') 把捕獲的內容命名爲group,並壓入堆棧(Stack)
  2. (?'-group') 從堆棧上彈出最後壓入堆棧的名爲group的捕獲內容,若是堆棧原本爲空,則本分組的匹配失敗
  3. (?(group)yes|no) 若是堆棧上存在以名爲group的捕獲內容的話,繼續匹配yes部分的表達式,不然繼續匹配no部分
  4. (?!) 零寬負向先行斷言,因爲沒有後綴表達式,如沒有(?!B)的B,試圖匹配老是失敗

在PHP中是支持(?(group)yes|no)語法的,這裏的group是分組編號,即子模式編號,如(A)?(?(1)yes|no) ,匹配Ayes 和 no

下面這裏引用《正則表達式30分鐘入門教程#平衡組》關於<>配對匹配的例子,展現平衡組用法,

 
  1. < #最外層的左括號
  2. [^<>]* #最外層的左括號後面的不是括號的內容
  3. (
  4. (
  5. (?'Open'<) #碰到了左括號,在黑板上寫一個"Open"
  6. [^<>]* #匹配左括號後面的不是括號的內容
  7. )+
  8. (
  9. (?'-Open'>) #碰到了右括號,擦掉一個"Open"
  10. [^<>]* #匹配右括號後面不是括號的內容
  11. )+
  12. )*
  13. (?(Open)(?!)) #在遇到最外層的右括號時,判斷黑板上還有沒有沒擦掉的"Open";若是還有,則匹配失敗
  14. > #最外層的右括號
  15. 平衡組的一個最多見的應用就是匹配HTML,下面這個例子能夠匹配嵌套的<div>標籤:
  16. <div[^>]*>[^<>]*(((?'Open'<div[^>]*>)[^<>]*)+((?'-Open'</div>)[^<>]*)+)*(?(Open)(?!))</div>
 

概念六:模式修飾符

模式修飾符在許多程序語言中都支持的,好比最多見的是i,不區分大小寫,如javascript裏的/[a-z0-9]/i,表示匹配字母數字,不區分大小寫。

本人在寫php正則時經常使用的模式修飾符主要有is,如: 
$pattern = '#[a-z0-9]+#is';

模式修飾符s的做用主要是的.可以匹配換行,在處理換行數據時,一般會用到。

在PHP中,模式修飾符有兩種用法,一種是上面的,在分隔符後面的模式修飾符,它的做用範圍是全局;另外一種是在正則表達式中間的。 
例如:

 
  1. 正則:/((?i)[A-Z]+)c/
  2. 測試字符:abcABC
  3. 匹配:abc
  4. 說明:局部(ab)的大小寫被忽略了,(?i)的做用範圍在分組1

若是把正則改爲:/([A-Z]+)c/i,則匹配結果將是:abcABC 
示例地址:PHP正則表達式中間的模式修飾符

關於PHP模式修飾符的講解,請查看PHP手冊中的《PHP模式修飾符》。

 

七:正則三段論應用

我眼裏的正則表達式(入門)》 和 本文《深刻正則表達式應用》幾乎傾盡本人正則學習所有思想,可是不少讀者反饋,看暈了!看到如此點評,實屬無奈,所以,有必要追加本節,來個總體統籌運用,但願能讓你們猶如撥雲見月,洞悉其中的精義。

正則三段論:定錨點,去噪點,取數據

兩篇文章中,最重要的部分當屬正則三段論:定錨點,去噪點,取數據,它是整個正則處理過程當中的靈魂,它貫穿整個正則撰寫過程。

下面舉例說明它的思想,示例六:

 
  1. 源數據:標題:深刻正則表達式應用,做者:Zjmainstay
  2. 需求:匹配做者名字

我要從源數據取到Zjmainstay這個做者名,那麼,在這裏,做者:就是咱們所說的錨點,由於在上面這段數據中它可以惟必定位到咱們的數據Zjmainstay(就在它後面),所以,咱們獲得

(1) 定錨點:做者:

而在這裏,咱們不須要關心標題什麼的,所以,標題:深刻正則表達式應用,就是咱們的噪點,所以,咱們獲得

(2) 去噪點

最後,咱們肯定做者:後面就是咱們的數據,這個數據能夠是任意字符,所以,咱們獲得正則: 
做者:(.*)

而噪點部分,由於不會對數據取值形成干擾,直接去掉,不須要引入正則中。

下面再舉一個噪點干擾的例子,示例七:

 
  1. 源數據: <a href="http://www.zjmainstay.cn/my-regexp" class="demo8" title="正則三段論應用舉例">正則表達式入門教程</a>
  2. 需求:提取連接和標題,還有a標籤的文字

看到這個源數據和需求,咱們必須定位好錨點,主要有: 
1. <a //必須是a標籤 
2. href=" 和 " //href=""的內容獲得連接 
3. title=" 和 " //title=""的內容獲得標題 
4. > 和 </a> //>和</a>的內容獲得標籤文字

而後,其餘的都是噪點,使用.*?替代,須要提取的數據部分使用括號獲取子模式,獲得分組數據,所以獲得正則: 
<a href="(.*?)".*?title="(.*?)">(.*?)</a>

看到這裏,也許有朋友以爲,我仍是不會寫,那麼,再來一個更簡單的構建方法,細化步驟,從源串逐步獲得正則,示例八:

 
  1. 1. 直接拷貝源串,特殊字符處理轉義(本例沒特殊字符)
  2. <a href="http://www.zjmainstay.cn/my-regexp" class="demo8" title="正則三段論應用舉例">正則表達式入門教程</a>
  3. 2. 從左到右,一段段轉化
  4. 2.1 <a href="(.*?)" class="demo8" title="正則三段論應用舉例">正則表達式入門教程</a>
  5. 2.2 <a href="(.*?)".*?title="正則三段論應用舉例">正則表達式入門教程</a>
  6. 2.3 <a href="(.*?)".*?title="(.*?)">正則表達式入門教程</a>
  7. 2.4 <a href="(.*?)".*?title="(.*?)">(.*?)</a>
  8. 3. 獲得最終的正則
  9. <a href="(.*?)".*?title="(.*?)">(.*?)</a>

至此,正則三段論的基本思想已經展現完畢,你們還有什麼不解請評論留言,本人看到會第一時間給予回覆。

熟悉正則三段論處理思想,剩下的即是基本語義的熟練程度了,這個經過多用多練能夠達到熟能生巧的境界。

最後留下一句至尊提醒:.是萬能字符,你們看着用,遇到換行使用[\s\S]替換.便可。

轉載請附帶本文原文地址: 深刻理解正則表達式高級教程,首發自  Zjmainstay學習筆記
相關文章
相關標籤/搜索