做者:西瓜玩偶(racnil070512 at hotmail dot com)
1、基礎知識
在PCRE正則表達式中,咱們能夠利用圓括號定義一個子組,咱們可使用preg_match函數(其餘函數的信息請參考PHP官方API文檔)的第三個參數捕獲圓括號中匹配的內容:
preg_match('#color\h*:\h*([A-Za-z]*)#', 'color: red', $matches);
print_r($matches);
運行的結果爲:
Array
(
[0] => color: red
[1] => red
)
根據定義,子組(正則表達式中圓括號)中的內容會按照左半邊括號出現的順序,將匹配的內容分別存放至$matches數組中,下標從1開始(下標0的內容爲整個匹配的字符串)。
這個特性可讓咱們很方便地從被匹配的字符串中提取咱們須要的信息。PCRE中的子組的功能其實很是強大,可是PHP官方的API文檔並無對齊做過多的介紹。下面的文章嘗試對PCRE中的子組功能作一個初步的介紹。
2、匹配順序
子組其中一個重要的做用就是用來描述「分支」的匹配,可是若是較短的分支是較長分支的前綴的話,那麼較短的分支必定要放在較長的分支後面:
'#(eq|lte|gte|lt|gt)#'正則表達式
注意,這裏的lt必須放在lte的後面,不然的話正則表達式解析器讀到lt時分支就已經匹配成功了,那麼lte就永遠不會被匹配到。
3、非捕獲子組
有些時候子組只是用來描述「分支」的匹配的,咱們並不想讓最後的$matches裏面出現括號裏的內容,此時能夠用非捕獲子組(?:)告訴正則表達式解析器,它不須要被捕獲:
'#(?:https?|ftp)://([A-Za-z\.]+)#'數組
這樣,URL裏面主機名部分就會被存放至$matches數組下標爲1的域內。而前面的https?|ftp雖然也被打了圓括號,可是因爲圓括號中有?:,因此並不會被保存到$matches中。
不過這裏僅僅是舉例子,在實際應用中,能夠調用parse_url函數來更好地完成獲取主機名的任務。
4、前向探測(Lookahead)
前向探測的目的是,在當前的點,向後讀入內容(對於讀取匹配內容的程序來講,它即將讀入的內容被稱爲「前」;可是對於閱讀者來講,即將讀入的內容被
稱爲「後」),判斷其是否與子組中的正則表達式相匹配。若是匹配,則繼續匹配後面的內容,不然匹配失敗。雖然前向探測會向後讀入內容,可是被讀入的內容並
不會被「消耗」掉,也不算作正則表達式匹配的一部分,也就是說,後面的正則表達式依然能夠匹配到向後讀入的內容。
若是這樣說不太明白,能夠看看下面的例子。利用(?=)就能夠構造一個前向探測:
'#\d*(?= mm)#'
這個正則表達式會匹配如'100 mm'這樣的字符串。因爲前向探測的正則表達式mm並不屬於正則表達式的一部分,因此最後整個表達式(注意,不是$matches下標爲1的域,而是整個表達式,也就是下標0)匹配出來的結果是'100'。
更好的例子是檢查密碼是否符合規範:
'#^(?=\w{8,20}$)(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^_]*_).*$#'
這個正則表達式在最開頭的地方依次使用了5個前向探測子組,分別檢查密碼長度在8至20之間、含有大寫字母、含有小寫字母、含有數字以及含有下劃線。只有當這五個條件都知足,正則表達式纔會繼續向下匹配。因爲這些子組都不會消耗讀入的內容,因此最後咱們簡單地使用一個.*就能夠獲取整個密碼字符串。
5、前向逆探測(Negative Lookahead)
與前向探測相似,只不過子組中的表達式必須不知足才行。它的構造方法爲(?!):
'#\d*(?!\d| mm)#'函數
這個表達式除了相似於'100 mm'之外其他的相似於'100 cm'這樣的字符串均可以被匹配。注意子組正則表達式裏面加了一個\d,由於不加它,當讀入'100 mm'的時候,表達式仍是會匹配到'10',這是由於'0 mm'不匹配' mm'。
6、後向探測(Lookbehind)
與前向探測相似,後向探測只不過是以當前點爲準,向前讀入內容。後向探測的構造方法爲(?<=):
'#(?<=EUR ).*#'
這個正則表達式會匹配'EUR 100'這樣的字符串。匹配結果爲'100'而不是'EUR 100',這是由於後向探測是以當前點爲準,向前讀入內容,這也就意味着,當開始進行最後.*的匹配時,'EUR '早已被讀過了。
不過這並不意味着後向探測會消耗內容,只是由於咱們並無在正則表達式中匹配'EUR '而已。若是你有興趣,能夠嘗試下面的表達式:
'#EUR (?<=EUR)\d*#'
這樣,匹配出來的結果就是'EUR 100'了。
7、後向逆探測(Negative Lookbehind)
與後向探測相似,只不過子組內的表達式必須不匹配。這裏就再也不舉例了。
8、命名子組
咱們能夠利用下面的語法命名一個子組:
'#(?P<prefix>A+)C#'url
它會匹配相似於'AAAAC'的字符串,子組匹配的內容'AAAA'不只會以數字下標保存(這個例子中爲1),亦會以字符串下標('prefix')保存在$matches裏面。
9、子組的重複利用
利用下面的方式咱們能夠重複利用已經在正則表達式中出現的子組:
'#(\w+) (?1)#'
這個正則表達式會匹配'foo bar'。不過須要注意的是,重用的子組並不會被捕獲。若是想要捕獲重用的子組,則應該在子組外面再加上一個括號:
'#(\w+) ((?1))#'
咱們甚至能夠經過子組名稱來重複利用它:
'#(?<pattern>\w+) (?&pattern)#'
甚至還能夠遞歸地調用子組:
'#(\w+, (?1)?)(\w+)#'
上面的表達式會匹配'foo, bar, baz, qux'。
10、重置分支
這一點在PHP官方文檔中已經提到了:
'#(?:(Sat)ur|(Sun))day#'
當匹配'Sunday'的時候,咱們會發如今$matches裏面下標爲1的域是空的,這是由於它嘗試過匹配(Sat),因爲沒有匹配到內容,因此它在$matches裏面加入了一個空的匹配項。若是要去掉這個惱人的匹配項,咱們須要在匹配不成功的時候重置分支:
'#(?|(Sat)ur|(Sun))day#'
將原來的冒號改成豎線以後,咱們就會發現,原來空的匹配不見了。
11、總結
上面的文章中介紹了PCRE中子組的使用方法,而且簡單地介紹了九種子組的特殊功能。若是可以靈活地、適當地運用在咱們的程序中,它就能夠幫助咱們省掉許多字符串處理的步驟。遞歸