【读书】正则指引-3-括号
生活随笔
收集整理的這篇文章主要介紹了
【读书】正则指引-3-括号
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
分組
? ? ? 小括號(以下稱為括號)可以提供的第一個功能就是分組(grouping)。? ? ? 通過之前的學(xué)習(xí),可以知道量詞用于限定之前元素的出現(xiàn)次數(shù),這個元素可能是一個字符,也可能是一個字符組,還可能是一個表達(dá)式。如果把一個表達(dá)式用括號包圍起來,就形成了括號內(nèi)的表達(dá)式,而這種表達(dá)式通常被稱為“子表達(dá)式”。
? ? ? 換句話說,子表達(dá)式就是利用括號分組功能形成的表達(dá)式。
? ? ??比如,(ab)+表示希望字符串a(chǎn)b重復(fù)出現(xiàn)一次以上。(對比ab+的含義)
? ? ? 分組的作用是可以準(zhǔn)確表達(dá)“長度只能是m或 n”的語義。
分組功能使用場景:
- 身份證號碼的準(zhǔn)確匹配 -- ^[1-9]\d{14}(\d{2}[0-9x])?$
- Open tag的(較)準(zhǔn)確匹配 -- ^<[^/]([^>]*[^/])?>$
- Web 服務(wù)中的 URL rewrite 功能 -- 略
- E-mail地址匹配 -- ^[-\w]{0,64}@([-a-zA-Z0-9]{1,63}\.)*[-a-zA-Z0-9]{1,63}$
多選結(jié)構(gòu)
? ? ? 解決15和18位身份證匹配問題其實(shí)有兩種思路。第一種(上面給出的正則表達(dá)式),是將18位號碼多出的3位“合并”到匹配15位號碼的表達(dá)式中。第二種,則是直接將15位和18位身份證號分開處理。同樣還是使用括號,但使用的是括號的第二個功能,多選結(jié)構(gòu)(alternative)。? ? ? 多選結(jié)構(gòu)的形式是(…|…),即在括號內(nèi)以豎線|分隔開多個子表達(dá)式,這些子表達(dá)式也叫做多選分支。在一個多選結(jié)構(gòu)內(nèi),多選分支的數(shù)目沒有限制。在匹配時,整個多選結(jié)構(gòu)被視為單個元素,只要其中某個子表達(dá)式能夠匹配,整個多選結(jié)構(gòu)的匹配就成功。如果所有子表達(dá)式都不能匹配,則整個多選結(jié)構(gòu)匹配失敗。
? ? ??所以,身份證號碼匹配的第二種方式為:([1-9]\d{14}|[1-9]\d{14}\d{2}[0-9x])
多選結(jié)構(gòu)使用場景:
- 匹配IP地址的一段 -- ([0-9]|[0-9]{2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
- 匹配手機(jī)號 -- (0|\+86)?(13[0-9]|15[0-356]|18[025-9])\d{8}
- 匹配月、日、小時、分鐘 -- 略
- tag的準(zhǔn)確匹配 -- <(‘[^’]*’|”[^”]*”|[^’”>])+>
- 多選結(jié)構(gòu)的一般表示法是(option1|option2),一般會同時使用括號()和豎線|,但是如果沒有括號,只出現(xiàn)豎線,仍然是正確的多選結(jié)構(gòu)。在多選結(jié)構(gòu)中,豎線用來分隔多選結(jié)構(gòu),而括號用來規(guī)定整個多選結(jié)構(gòu)的范圍,如果沒有出現(xiàn)括號,則將整個表達(dá)式視為一個多選結(jié)構(gòu)。所以ab|cd等價于(ab|cd)。推薦明確寫出兩端的括號,可以更加形象,也能避免一些隱晦的錯誤(因?yàn)樨Q線的優(yōu)先級很低)。
- 多選分支并不等于字符組,雖然看起來類似字符組,如[abc]能匹配的字符串和(a|b|c)一樣。從理論上講,可以完全用多選結(jié)構(gòu)來替換字符組,但這種做法不被推薦。理由:首先,[abc]比(a|b|c)簡潔,字符組有范圍表示法;其次,大多數(shù)情況下,[abc]比(a|b|c)的效率要高的多。反過來,多選結(jié)構(gòu)不一定能對應(yīng)到字符組。因?yàn)樽址M的每個“分支”的長度相同,而且只能是單個字符;而多選結(jié)構(gòu)的每個“分支”的長度沒有限制,甚至可以是復(fù)雜的表達(dá)式,比如(abc|b+c*ab),字符組對這種匹配完全無能為力。
- 字符組有排除型字符組,但多選結(jié)構(gòu)卻沒有對應(yīng)的結(jié)構(gòu)來表達(dá)該語義。
- 多選分支的排列是有講究的。例如,表達(dá)式(jeff|jeffrey),用它匹配jeffrey,結(jié)果是jeff還是jeffrey呢?這個問題其實(shí)沒有標(biāo)準(zhǔn)答案,大多數(shù)語言的多選結(jié)構(gòu)都會按照優(yōu)先選擇最左側(cè)的分支來進(jìn)行匹配。
- 在平時使用中,如果出現(xiàn)多選結(jié)構(gòu),應(yīng)當(dāng)盡量避免多選分支中存在重復(fù)匹配,因?yàn)檫@樣會大大增加回溯的計(jì)算量。
引用分組
? ? ? 括號不僅僅能把有聯(lián)系的元素歸攏起來并分組,還有其他作用,即使用括號之后,正則表達(dá)式會保存每個分組真正匹配的文本,等到匹配完成后,可以通過group(num)之類的方法“引用”分組在匹配時捕獲的內(nèi)容。其中num表示對應(yīng)的編號,括號分組的編號規(guī)則是從左向右計(jì)數(shù),從1開始。這就是括號的第三個功能,捕獲分組(capturing group)。這種括號叫做捕獲型括號。? ? ? 一般來說,正則表達(dá)式匹配完成之后,都會得到一個表示“匹配結(jié)果”的對象,對它調(diào)用獲取分組的方法,傳入分組編號num,就可以得到對應(yīng)分組匹配的文本。num的編號是從1開始的,不過編號為0的分組也是默認(rèn)存在的,其對應(yīng)整個表達(dá)式匹配的文本。
? ? ? 關(guān)于括號存在嵌套的問題:無論括號如何嵌套,分組的編號都是根據(jù)開括號出現(xiàn)順序來計(jì)數(shù)的,開括號是從左向右起第多少個開括號,整個括號分組的編號就是多少。
引用分組是使用場景:
- 提取HTML中的所有超鏈接的地址和文本 -- <a\s+href\s*=\s*[“’]?([^”’\s]+)[“’]?>([^<]+)</a>
- 提取HTML中的標(biāo)題相關(guān)內(nèi)容 -- <head>([^<]+)</head>
- 提取HTML中的圖片鏈接相關(guān)內(nèi)容 -- <img\s+[^>]*?src=[‘”]?([^”’\s]+)[‘”]?[^>]*>
在提取日期 2010-12-22 這個內(nèi)容時,正確的正則表達(dá)式為
(\d{4})-(\d{2})-(\d{2})
而錯誤的表達(dá)式可能被寫成
(\d){4}-(\d{2})-(\d{2})
引用分組捕獲的文本,不僅僅用于數(shù)據(jù)提取,也可以用于替換。
? ? ? 在各種語言中都有類似substitute(pattern,replacement,string)這種形式的正則替換命令。其中replacement處可以使用引用分組,形式是\num(有些語言中為$num,下文同,本文中只寫作\num),其中num為對應(yīng)分組的編號。同時要明白replacement處為普通字符串,在某些語言中,需要特別指明replacement使用原生字符串才行,否則\num這種引用分組在普通字符串中會被當(dāng)做不合法的轉(zhuǎn)義序列處理。
? ? ? 另外,如果想在replacement中引用整個表達(dá)匹配的文本,不能使用\0,即便使用原生字符串也不行。一種變通策略是給整個表達(dá)式加上一對括號,之后使用\1來引用。
反向引用
? ? ? 反向引用(back-reference)主要用于檢測重疊出現(xiàn)的內(nèi)容,即允許在正則表達(dá)式內(nèi)部引用之前的捕獲分組匹配的文本。其使用形式也是\num。反向引用的使用場景:
- 檢查某個單詞是否包含重疊出現(xiàn)的字母 -- ^([a-z])\1$
- 解析HTML代碼時匹配簡單tag -- <([^>]+)>[\s\S]*?</\1>
- 解析HTML代碼時匹配復(fù)雜tag -- <([a-zA-Z0-9]+)(\s[^>]+)?>[\s\S]*?</\1>
- 處理中文文本 -- 略
各種引用的記法
? ? ? 上面說過,對分組進(jìn)行引用的時候使用\num這類表示法,同時還說過,還有另外一些語言使用了不同的表示方式。如下圖所示? ? ? 一般情況下,我們認(rèn)為$num要好于\num。原因在于,$0可以準(zhǔn)確表示“第0個分組(也就是整個表達(dá)式匹配的文本)”,而\0則不行,因?yàn)椴簧僬Z言的字符串中,\num本身可能就是一個有意義的轉(zhuǎn)義序列,表示值為num的ASCII碼字符,所以\0會被解釋成“ASCII編碼為0的字符”。
? ? ? 二義性問題:無論采用\num還是$num進(jìn)行引用都會遇到二義性問題,如果出現(xiàn)了\10,它到底表示第10個捕獲分組\10,還是第1個捕獲分組\1之后跟著一個字符0?
? ? ? 各種語言的解決方式有所不同,Python和PHP中提供了特定的表示法來專門解決這類問題(細(xì)節(jié)略)。而Java、Ruby和JavaScript中則是通過制定規(guī)則來解決這類問題的:如果是一位數(shù),則引用對應(yīng)的捕獲分組;如果是兩位數(shù)且存在對應(yīng)捕獲分組時,引用對應(yīng)的捕獲分組,如果不存在對應(yīng)的捕獲分組,則引用一位數(shù)編號的捕獲分組。
? ? ??簡單思考一下就可以發(fā)現(xiàn),后面這幾種語言的策略存在一個問題無法解決:如果存在編號為10的捕獲分組,無法用\10表示“編號為1的捕獲分組和字符0”,因?yàn)榇藭r\10表示的必然是編號為10的捕獲分組。在實(shí)際開發(fā)中,尤其是進(jìn)行文本替換時有時確實(shí)會遇到這個問題,在現(xiàn)有的規(guī)則下是無解的。所幸的是,一般情況下,我們根本不會用到太多的捕獲分組(即基本上用不到\10以上的分組);另外,已經(jīng)有越來越多的語言提供了命名分組,它可以徹底解決這個問題。
命名分組
? ? ? 使用數(shù)字編號來標(biāo)識捕獲分組存在不夠直觀,括號多了容易搞錯序號,引用時不夠方便的問題。所以,一些語言和工具提供了命名分組(named grouping),可以將它看成另一種捕獲分組。? ? ? 命名分組在每種語言中各有不同,此處不做展開。另外,由于歷史原因和后向兼容性,即便使用了命名分組,每個命名分組同時也具有數(shù)字編號,其編號規(guī)則沒有變化。
非捕獲分組
? ? ? 前面介紹了括號的三種主要用途,需要指出的是,各種用途間不是彼此獨(dú)立的,而是互相重疊的:單純的分組可以視為“只包含一個多選分支的多選結(jié)構(gòu)”;整個多選結(jié)構(gòu)也會被視為單個元素,可以由單個量詞限定。最重要的是,無論是否需要引用分組,只要出現(xiàn)了括號,正則表達(dá)式在匹配時就會把括號內(nèi)的子表達(dá)式存儲起來,提供引用。如果并不需要引用,保存這些信息無疑會影響正則表達(dá)式的性能;如果表達(dá)式比較復(fù)雜,要處理的文本又很多,更可能嚴(yán)重影響性能。? ? ? 為解決這種問題,正則表達(dá)式提供了非捕獲分組(non-capturing group),非捕獲分組類似普通的捕獲分組,只是在開括號后緊跟一個問號和冒號(?:…),這樣的括號叫做非捕獲型括號。它的作用只是限定量詞的作用范圍,不捕獲任何文本。在引用分組時,分組的編號同樣會按照開括號出現(xiàn)的順序從左向右遞增,只是必須以捕獲分組為準(zhǔn),非捕獲分組會略過。
補(bǔ)充
括號的轉(zhuǎn)義
? ? ? 括號的轉(zhuǎn)義要求和括號相關(guān)的所有三個元字符(、)、|都必須轉(zhuǎn)義\(、\)、\|。URL Rewrite
? ? ? URL Rewrite是常見web服務(wù)器都具備的功能,用來進(jìn)行網(wǎng)址的轉(zhuǎn)發(fā)。例子如下外部訪問 URL
http://www.example.com/blog/2006/12
內(nèi)部實(shí)現(xiàn)
http://www.example.com/blog/posts.php?year=2006&month=12
? ? ? 這樣重寫的好處是隔離了外部接口和內(nèi)部實(shí)現(xiàn),方便修改;也有利于提供更有意義、更直觀的URL。
? ? ? 一般來說,URL Rewrite都是使用轉(zhuǎn)發(fā)規(guī)則實(shí)現(xiàn)的,每條轉(zhuǎn)發(fā)規(guī)則對應(yīng)一類URL,以正則表達(dá)式解析并提取出所有需要的信息,重組之后再轉(zhuǎn)發(fā)。
? ? ? 學(xué)習(xí)URL Rewrite的使用可以參考當(dāng)前的各類主流Web服務(wù)器的配置,此處略。
總結(jié)
? ? ? 本篇將《正則指引》的第三章內(nèi)容進(jìn)行了概括總結(jié),下次講解正則表達(dá)式中的斷言。參考資料
? ? ? 《正則指引》:余晟。?轉(zhuǎn)載于:https://my.oschina.net/moooofly/blog/377757
總結(jié)
以上是生活随笔為你收集整理的【读书】正则指引-3-括号的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot项目根据不同的环境启
- 下一篇: LINQPad工具-linq、sql、I