一文搞懂正则表达式
最近在學習正則表達式,找了很多文章都很混亂,花了好大功夫終于搞懂了,因此在這里出一個系統(tǒng)性的教程,系統(tǒng)對讀者有幫助。
參考文章:https://www.liujiangblog.com/course/python/73
在線正則表達式測試器:https://tool.oschina.net/regex/
總結(jié)
正則表達式的字符總共分為以下5種,這里原文檔的講解有點問題和錯誤,不便于理解,因此我自己調(diào)換了些順序,便于理解。5種正則表達式中,我根據(jù)其特性分為字符表達式,輔助表達式,其中輔助表達式分為字符數(shù)量輔助與字符位置輔助,字符表達式即我們用來匹配正則表達式的各種字符的代號。
- 數(shù)量表達式主要用來表達需要很多重復字符表達的時候只需要一個字符就可以表達很多重復的字符,比如例如手機號碼13666666666,一般的新手可能會寫成\d\d\d\d\d\d\d\d\d\d\d(注意,這不是一個恰當?shù)谋磉_式,其中\(zhòng)d是可以表達6的一種正則字符),不但寫著費勁,看著也累,還不一定準確恰當。這種情況可以使用表達式再加上修飾匹配次數(shù)的特殊符號{},不用重復書寫表達式就可以重復匹配。比如[abcd][abcd]可以寫成[abcd]{2};
- 位置表達式主要用于表達對應字符的位置,比如說希望匹配學號A008,A字母一定是在開頭,因此需要對應的位置限制符
除了以上兩種表達式以外,正則表達式中還存在一種比較特別的字符,稱為元字符,這種字符用來表達一些特殊的含義與功能,比如說字符匹配的優(yōu)先級(比如(ab),用了括號括起來以后操作是對ab兩者同時做),或者一些邏輯操作,比如[abc]是匹配abc三者的任意一個。注意,在正則表達式之中優(yōu)先級非常重要,這個在最后會介紹,
詳細介紹
字符表達式
普通字符
字母、數(shù)字、漢字、下劃線、以及沒有特殊定義的符號,都是"普通字符"。正則表達式中的普通字符,在匹配的時候,只匹配與自身相同的一個字符。
例如:表達式c,在匹配字符串abcde時,匹配結(jié)果是:成功;匹配到的內(nèi)容是c;匹配到的位置開始于2,結(jié)束于3。(注:下標從0開始還是從1開始,因當前編程語言的不同而可能不同)
預定義匹配字符集
這個集合是通過一些字符來匹配同一組普通字符。可以同時匹配某個預定義字符集中的任意一個字符。比如,表達式\d可以匹配任意一個數(shù)字。雖然可以匹配其中任意字符,但是只能是一個,不是多個。如下表所示,注意大小寫:
| 表達式 | 匹配 | 
| \d | 任意一個數(shù)字,0~9 中的任意一個 | 
| \w | 任意一個字母或數(shù)字或下劃線,也就是 A~Z,a~z,0~9,_ 中的任意一個 | 
| \s | 空格、制表符、換頁符等空白字符的其中任意一個 | 
| \D | \d的反集,也就是非數(shù)字的任意一個字符,等同于[^\d] | 
| \W | \w的反集,也就是[^\w] | 
| \S | \s的反集,也就是[^\s] | 
例如表達式\d\d,在匹配abc123時,匹配的結(jié)果是:成功;匹配到的內(nèi)容是12;匹配到的位置開始于3,結(jié)束于5。
轉(zhuǎn)義字符
一些無法書寫或者具有特殊功能的字符,采用在前面加斜杠"\"進行轉(zhuǎn)義的方法。這些字符在之后的輔助字符或者元字符中會有單獨的作用,因此不能直接加進去,需要轉(zhuǎn)義,否則會破壞正則表達式的含義。例如下表所示
| 表達式 | 匹配 | 
| \r,?\n | 匹配回車和換行符 | 
| \t | 匹配制表符 | 
| \\ | 匹配斜杠\ | 
| \^ | 匹配^符號 | 
| \$ | 匹配$符號 | 
| \. | 匹配小數(shù)點. | 
尚未列出的還有問號?、星號*和括號等其他的符號。所有正則表達式中具有特殊含義的字符在匹配自身的時候,都要使用斜杠進行轉(zhuǎn)義。這些轉(zhuǎn)義字符的匹配方法與普通字符類似,也是匹配與之相同的一個字符。
例如表達式$d,在匹配字符串"abc?𝑑𝑒"時,匹配結(jié)果是:成功;匹配到的內(nèi)容是𝑑𝑒"時,匹配結(jié)果是:成功;匹配到的內(nèi)容是?d;匹配到的位置開始于3,結(jié)束于5。
?
輔助表達式
重復匹配
前面的表達式,無論是只能匹配一種字符的表達式,還是可以匹配多種字符其中任意一個的表達式,都只能匹配一次。但是有時候我們需要對某個片段進行重復匹配,例如手機號碼13666666666,一般的新手可能會寫成\d\d\d\d\d\d\d\d\d\d\d(注意,這不是一個恰當?shù)谋磉_式),不但寫著費勁,看著也累,還不一定準確恰當。這種情況可以使用表達式再加上修飾匹配次數(shù)的特殊符號{},不用重復書寫表達式就可以重復匹配。比如[abcd][abcd]可以寫成[abcd]{2}。
| 表達式 | 匹配 | 
| {n} | 表達式重復n次,比如\d{2}相當于\d\d,a{3}相當于aaa | 
| {m,n} | 表達式至少重復m次,最多重復n次。比如ab{1,3}可以匹配ab或abb或abbb | 
| {m,} | 表達式至少重復m次,比如\w\d{2,}可以匹配a12,_1111,M123等等 | 
| ? | 匹配表達式0次或者1次,相當于{0,1},比如a[cd]?可以匹配a,ac,ad | 
| + | 表達式至少出現(xiàn)1次,相當于{1,},比如a+b可以匹配ab,aab,aaab等等 | 
| * | 表達式出現(xiàn)0次到任意次,相當于{0,},比如\^*b可以匹配b,^^^b等等 | 
其中有些例子一定要注意!比如ab{1,3}中重復的是b而不是ab,(ab){1,3}這樣重復的才是ab。表達式\^*b中重復的是\^而不是^,要從左往右讀正則表達式,轉(zhuǎn)義符號有更高的優(yōu)先級,需要和后面的字符整體認讀。
表達式\d+\.?\d*在匹配It costs $12.5時,匹配的結(jié)果是:成功;匹配到的內(nèi)容是12.5;匹配到的位置開始于10,結(jié)束于14。
表達式go{2,8}gle在匹配Ads by goooooogle時,匹配的結(jié)果是:成功;匹配到的內(nèi)容是goooooogle;匹配到的位置開始于7,結(jié)束于17。
注意:非常重要,重復表達式只對前面的一個或者一組字符起作用,意思是如果有括號,那就一起起作用,高優(yōu)先級可以先于低優(yōu)先級操作。這一點對理解下面的貪婪模式與非貪婪模式很重要!
貪婪模式與非貪婪模式
在重復匹配時,正則表達式默認總是盡可能多的匹配,這被稱為貪婪模式。意思是只要滿足重復前后的字符一致,那么正則表達式就會匹配最多的那種。比如,針對文本dxxxdxxxd,有滿足前后都是d的有兩種匹配模式,第一種是dxxxd,第二種是dxxxdxxxd, 而表達式(d)(\w+)(d)中的\w+將匹配第一個d和最后一個d之間的所有字符xxxdxxx。可見,\w+在匹配的時候,總是盡可能多的匹配符合它規(guī)則的字符。同理,帶有?、*和{m,n}的重復匹配表達式都是盡可能地多匹配。這就叫做貪婪模式。但是有時候,這種模式不是我們想要的結(jié)果,比如最常見的HTML標簽匹配。假設(shè)有如下的字符串
(這里原文檔錯誤了,原文檔中用的是如下的表示:
這里每個td后面都有一個換行符,用源文檔的語句跑出來也是分開的結(jié)果,并不會連續(xù),修改以后應該用如下所示的式子):
<table><tr><td>蘋果</td><td>桃子</td><td>香蕉</td></tr></table>
我們的意圖是獲取每個<td></td>標簽中的元素內(nèi)容,那么如果你將正則表達式寫成<td>(.*)</td>的話,你得到的是<td>蘋果</td><td>桃子</td><td>香蕉</td>這么個東西,而不是
<td>蘋果</td>
<td>桃子</td>
<td>香蕉</td>。(即匹配出了三個項,而不是一次全匹配了出來,因為三個或者一個開頭結(jié)尾都一致)
注意:不是說重復匹配只對前面的一個或者一組字符起作用嗎?為什么這里要求前后的字符?這里就體現(xiàn)了上面的重復匹配只對前面的起作用的原則的重要姓,如果不明確這一點非常容易混淆。
在這里這個原則其實并沒有被打破,+起作用的只是w本身,不包括前后的d,因此重復匹匹配符并不會對前后的產(chǎn)生要求,真正產(chǎn)生要求的是要求盡量多匹配的貪婪模式,因為重復匹配字符只是要求了至少出現(xiàn)的次數(shù),但沒表明最多出現(xiàn)的次數(shù),所以貪婪模式會持續(xù)匹配直到最后找到滿足前后要求的最長序列。
因此在這個時候我們就要使用非貪婪模式:?在修飾匹配次數(shù)的特殊符號后再加上一個?問號,則可以使匹配次數(shù)不定的表達式盡可能少的匹配,使可匹配可不匹配的表達式,盡可能的"不匹配"。如果少匹配就會導致整個表達式匹配失敗的時候,與貪婪模式類似,非貪婪模式會最小限度的再多匹配一些,以使整個表達式匹配成功。
表達式<td>(.*?)</td>匹配上面的字符串時,將只得到<td>蘋果</td>,再次匹配下一個時,可以得到<td>桃子</td>,以此類推。
針對文本"dxxxdxxxd"舉例:
表達式(d)(\w+?)中的\w+?將盡可能少的匹配第一個d之后的字符,結(jié)果是只匹配了一個"x",整體只匹配了dx。表達式(d)(\w+?)(d)為了讓整個表達式匹配成功,\w+?不得不匹配xxx才可以讓后邊的d匹配,從而使整個表達式匹配成功。因此,結(jié)果是\w+?匹配了xxx,整體匹配了dxxx。
注意:如以上所說,貪婪模式會對匹配序列的前后位置有要求,非貪婪模式也如此,但只是對前后緊鄰的字符或者字符組合有要求,對更前面的沒有要求。
?
位置匹配
有時候,我們對匹配出現(xiàn)的位置有要求,比如開頭、結(jié)尾、單詞之間等等。
| 表達式 | 匹配 | 
| ^ | 在字符串開始的地方匹配,符號本身不匹配任何字符 | 
| $ | 在字符串結(jié)束的地方匹配,符號本身不匹配任何字符 | 
| \b | 匹配一個單詞邊界,也就是一個位置前后字符不全是\W的位置,與下面的\B相對應,符號本身不匹配任何字符。注意,這里原作者解釋有誤,不是字符與空格之間的位置。 | 
| \B | 匹配非單詞邊界,即左右兩邊都是\w范圍或者左右兩邊都不是\w范圍時的字符縫隙 | 
例如表達式^aaa在匹配xxx aaa xxx時,匹配結(jié)果是:失敗。因為^要求在字符串開始的地方匹配。
表達式aaa?在匹配𝑥𝑥𝑥?𝑎𝑎𝑎?𝑥𝑥𝑥時,匹配結(jié)果是:失敗。因為在匹配𝑥𝑥𝑥𝑎𝑎𝑎𝑥𝑥𝑥時,匹配結(jié)果是:失敗。因為?要求在字符串結(jié)束的地方匹配。
表達式.\b.在匹配@@@abc時,匹配結(jié)果是:成功;匹配到的內(nèi)容是@a,因為在@和a的中間位置滿足前后不都為\w的條件,因此匹配到這個位置;匹配到的位置開始于2,結(jié)束于4。
表達式\bend\b在匹配weekend,endfor,end時,匹配結(jié)果是:成功;匹配到的內(nèi)容是end;匹配到的位置開始于15,結(jié)束于18。注意這里每個\b的前后都要滿足前后不都為\w的條件,因此weekend和endfordou不行,weekend的ebu符合,endfor的d不符合
這里后面兩個比較難以理解,我采取了另外一份資料的解釋(原網(wǎng)址:https://www.cnblogs.com/litmmp/p/4925374.html),并通過我自己的理解寫出來:
原表述:一個位置前后字符不全是\W的位置
首先需要明確什么是位置:
It's a nice day today.
'I' 占一個位置,'t' 占一個位置,所有的單個字符(包括不可見的空白字符)都會占一個位置,這樣的位置我給它取個名字叫“顯式位置”。
注意:字符與字符之間還有一個位置,例如 'I' 和 't' 之間就有一個位置(沒有任何東西),這樣的位置我給它取個名字叫“隱式位置”。
“隱式位置”就是 \b 的關(guān)鍵!通俗的理解,\b 就是“隱式位置”。
此時,再來理解一下這句話:
如果需要更精確的說法,\b 匹配這樣的位置:它的前一個字符和后一個字符不全是(一個是,一個不是或不存在) \w。
翻譯一下這句話:
“隱式位置” \b,匹配這樣的位置:它的前一個“顯式位置”字符和后一個“顯式位置”字符不全是 \w。
這樣理解起來就方便多了
用以下例子表示:
就用 "It's a nice day today." 舉例說明:
正確的正則:\bnice\b
分析:第一個 \b 前面一個字符是空格,后面一個字符是 'n',不全是 \w,所以可以匹配出 'n' 是一個單詞的開頭。第二個 \b 前面一個字符是 'e',后面一個字符是空格,不全是 \w,可以匹配出 'e' 是一個單詞的結(jié)尾。所以,合在一起,就能匹配出以 'n' 開頭以 'e' 結(jié)尾的單詞,這里就能匹配出 "nice" 這個單詞。
錯誤的正則:a\bnice
分析:我見過有人類似于這樣來寫正則,想要達到的目的是匹配出上一個單詞以 'a' 結(jié)尾,下一個單詞以 'n' 開頭的部分,這里想匹配出 "a nice"。但是這個正則表達的可不是這個目的,\b 前面是字符 'a',后面是字符 'n',兩個都是“顯式字符”,顯然違背了 \b 的含義,所以這就是個錯誤的表達式,匹配不出任何東西。想要匹配出 "a nice",正確的正則寫法是:a\b.\bnice(不能換行)
?
?
元字符
元字符表達了很多特殊功能,其中最重要的是要注意這些字符的優(yōu)先級,其中括號可以將上面所說的幾種字符都包在一起作為一個字符組合對待:
| 表達式 | 匹配 | 
| . | 小數(shù)點可以匹配除了換行符\n以外的任意一個字符 | 
| | | 邏輯或操作符 | 
| [] | 匹配字符集中的一個字符 | 
| [^] | 對字符集求反,也就是上面的反操作。尖號必須在方括號里的最前面 | 
| - | 定義[]里的一個字符區(qū)間,例如[a-z] | 
| \ | 對緊跟其后的一個字符進行轉(zhuǎn)義 | 
| () | 對表達式進行分組,將圓括號內(nèi)的內(nèi)容當做一個整體,并獲得匹配的值。注意將括號當成整體的作用,這代表括號內(nèi)的字符可以被當成一個字符看待 | 
例如:
a.c匹配abc
(a|b)c匹配ac與bc
注意:這里(a|b)和[ab]實際上一樣,與的含義即為能匹配則匹配,不用一定要同時出現(xiàn),(a|b)這種表達實際上是為了完成(ab|c)這種[abc]完不成的表達
[abc]1匹配a1或者b1或者c1
使用方括號[]包含一系列字符,能夠匹配其中任意一個字符。用[^]包含一系列字符,則能夠匹配其中字符之外的任意一個字符。
[ab5@]匹配a或b或5或@
[^abc]匹配a,b,c之外的任意一個字符
注意:^只對后面的字符起作用
[f-k]匹配f~k?之間的任意一個字母
[^A-F0-3]匹配A~F以及0~3之外的任意一個字符
?
以下為優(yōu)先級列表:
可以看見,括號優(yōu)先級很高
反向引用
反向引用是一種特殊的()的使用方法,因此放在元字符之后介紹。
表達式在匹配時,小括號()包含的表達式所匹配到的字符串會被記錄并保存在系統(tǒng)中等之后使用。在獲取匹配結(jié)果的時候,之前的()包含的表達式所匹配到的字符串可被單獨重新獲取。這是一個非常有用也非常重要的特性。在實際應用場合中,當用某種邊界條件來查找(比如html變成中要求一個字符串前后必須為td對),而所要獲取的內(nèi)容又不指定對應的邊界時,必須使用小括號來指定所要的范圍。比如前面的?<td>(.*?)</td>",因為不缺定前后是什么,只知道是一對,因此可以用小括號構(gòu)成一個邊界符號對。
?
"小括號包含的表達式所匹配到的字符串"可以在匹配過程中直接使用。表達式后邊的部分,可以引用前面"括號內(nèi)的子匹配已經(jīng)匹配到的字符串"。引用方法是\加上一個數(shù)字。\1引用第1對括號內(nèi)匹配到的字符串,\2?引用第2對括號內(nèi)匹配到的字符串……以此類推,如果一對括號內(nèi)包含另一對括號,則外層的括號先排序號。換句話說,哪一對的左括號"("在前,那這一對就先排序號。舉例如下:
表達式('|")(.*?)(\1)在匹配'Hello', "World"時,匹配結(jié)果是:成功;匹配到的內(nèi)容是'Hello'。再次匹配下一個時,可以匹配到?"World"。這里的(\1),動態(tài)的引用了('|")匹配到的結(jié)果。
表達式(\w)\1{4,}在匹配aa bbbb abcdefg ccccc 111121111 999999999時,匹配結(jié)果是:成功;匹配到的內(nèi)容是ccccc。再次匹配下一個時,將得到999999999。這個表達式要求\w范圍的字符至少重復5次,注意與\w{5,}之間的區(qū)別。這里\1代表一個另外的\w,因此是重復五次
?
表達式<(\w+)\s*(\w+(=('|").*?\4)?\s*)*>.*?</\1>在匹配<td id='td1' style="bgcolor:white"></td>時,匹配結(jié)果是成功。如果<td>與</td>不配對,則會匹配失敗;如果改成其他配對,也可以匹配成功。這就是常用的HTML標簽匹配方法。
?
這里博主詳細解釋一下這個正則表達式。這個式子先拆出幾個括號,找到優(yōu)先級高的一下4個字符串:
1, (\w+)
?2,('|")
3, (=('|").*?\4)
4,? (\w+(=('|").?\4)?\s)
其中,2式指的是'或者”,放到3式中的\4引用的就是2式(因為2式是第四個左括號),然后在3式中的.*?表示了最小的前后具有引號對的字符串,在實例字符串中代表了'td1'或者"bgcolor:white"
后在4式中在3式的后面加了?與\S,因此表示3式只出現(xiàn)0或1次,限制其最多1次,后接空格。所以整個4式就代表最多出現(xiàn)一次的后接空格的3式子,即代表'td1' 或者"bgcolor:white"
原正則表達式中在4式后加了*,因此表示越多越好,所以實例字符串中的兩個引號串都被表達了進來
再之后就是前后的格式標識符,最后的</\1>引用了第一個(\w+)表達了前后的字符串對,這樣整體就表達出來了。
總結(jié)
 
                            
                        - 上一篇: 阿里聚安全 博客 ------
- 下一篇: 把老婆训练成女黑客的漏洞大神黄正|宅客故
