課程
/后端開發(fā)
/PHP
/PHP進階篇
老是看到有提到子組的,想來想去想不明白,頭皮都抓破三張
2016-08-06
源自:PHP進階篇 3-6
正在回答
在PCRE正則表達式中,我們可以利用圓括號定義一個子組,我們可以使用preg_match函數(shù)(其他函數(shù)的信息請參考PHP官方API文檔)的第三個參數(shù)捕獲圓括號中匹配的內(nèi)容:preg_match('#color\h*:\h*([A-Za-z]*)#', 'color: red', $matches);print_r($matches);運行的結(jié)果為:Array(??? [0] => color: red??? [1] => red)根據(jù)定義,子組(正則表達式中圓括號)中的內(nèi)容會按照左半邊括號出現(xiàn)的順序,將匹配的內(nèi)容分別存放至$matches數(shù)組中,下標(biāo)從1開始(下標(biāo)0的內(nèi)容為整個匹配的字符串)。這個特性可以讓我們很方便地從被匹配的字符串中提取我們需要的信息。PCRE中的子組的功能其實非常強大,但是PHP官方的API文檔并沒有對齊作過多的介紹。下面的文章嘗試對PCRE中的子組功能做一個初步的介紹。二、匹配順序子組其中一個重要的作用就是用來描述“分支”的匹配,但是如果較短的分支是較長分支的前綴的話,那么較短的分支一定要放在較長的分支后面:'#(eq|lte|gte|lt|gt)#'
注意,這里的lt必須放在lte的后面,否則的話正則表達式解析器讀到lt時分支就已經(jīng)匹配成功了,那么lte就永遠(yuǎn)不會被匹配到。三、非捕獲子組有些時候子組只是用來描述“分支”的匹配的,我們并不想讓最后的$matches里面出現(xiàn)括號里的內(nèi)容,此時可以用非捕獲子組(?:)告訴正則表達式解析器,它不需要被捕獲:'#(?:https?|ftp)://([A-Za-z\.]+)#'
這樣,URL里面主機名部分就會被存放至$matches數(shù)組下標(biāo)為1的域內(nèi)。而前面的https?|ftp雖然也被打了圓括號,但是由于圓括號中有?:,所以并不會被保存到$matches中。不過這里僅僅是舉例子,在實際應(yīng)用中,可以調(diào)用parse_url函數(shù)來更好地完成獲取主機名的任務(wù)。四、前向探測(Lookahead)前向探測的目的是,在當(dāng)前的點,向后讀入內(nèi)容(對于讀取匹配內(nèi)容的程序來說,它即將讀入的內(nèi)容被稱為“前”;但是對于閱讀者來說,即將讀入的內(nèi)容被稱為“后”),判斷其是否與子組中的正則表達式相匹配。如果匹配,則繼續(xù)匹配后面的內(nèi)容,否則匹配失敗。雖然前向探測會向后讀入內(nèi)容,但是被讀入的內(nèi)容并不會被“消耗”掉,也不算做正則表達式匹配的一部分,也就是說,后面的正則表達式依然可以匹配到向后讀入的內(nèi)容。如果這樣說不太明白,可以看看下面的例子。利用(?=)就可以構(gòu)造一個前向探測:'#\d*(?= mm)#'這個正則表達式會匹配如'100 mm'這樣的字符串。由于前向探測的正則表達式mm并不屬于正則表達式的一部分,所以最后整個表達式(注意,不是$matches下標(biāo)為1的域,而是整個表達式,也就是下標(biāo)0)匹配出來的結(jié)果是'100'。更好的例子是檢查密碼是否符合規(guī)范:'#^(?=\w{8,20}$)(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^_]*_).*$#'這 個正則表達式在最開頭的地方依次使用了5個前向探測子組,分別檢查密碼長度在8至20之間、含有大寫字母、含有小寫字母、含有數(shù)字以及含有下劃線。只有當(dāng) 這五個條件都滿足,正則表達式才會繼續(xù)向下匹配。由于這些子組都不會消耗讀入的內(nèi)容,所以最后我們簡單地使用一個.*就可以獲取整個密碼字符串。五、前向逆探測(Negative Lookahead)與前向探測類似,只不過子組中的表達式必須不滿足才行。它的構(gòu)造方法為(?!):'#\d*(?!\d| mm)#'
這個表達式除了類似于'100 mm'以外其余的類似于'100 cm'這樣的字符串都可以被匹配。注意子組正則表達式里面加了一個\d,因為不加它,當(dāng)讀入'100 mm'的時候,表達式還是會匹配到'10',這是因為'0 mm'不匹配' mm'。六、后向探測(Lookbehind)與前向探測類似,后向探測只不過是以當(dāng)前點為準(zhǔn),向前讀入內(nèi)容。后向探測的構(gòu)造方法為(?<=):'#(?<=EUR ).*#'這個正則表達式會匹配'EUR 100'這樣的字符串。匹配結(jié)果為'100'而不是'EUR 100',這是因為后向探測是以當(dāng)前點為準(zhǔn),向前讀入內(nèi)容,這也就意味著,當(dāng)開始進行最后.*的匹配時,'EUR '早已被讀過了。不過這并不意味著后向探測會消耗內(nèi)容,只是因為我們并沒有在正則表達式中匹配'EUR '而已。如果你有興趣,可以嘗試下面的表達式:'#EUR (?<=EUR)\d*#'這樣,匹配出來的結(jié)果就是'EUR 100'了。七、后向逆探測(Negative Lookbehind)與后向探測類似,只不過子組內(nèi)的表達式必須不匹配。這里就不再舉例了。八、命名子組我們可以利用下面的語法命名一個子組:'#(?P<prefix>A+)C#'
它會匹配類似于'AAAAC'的字符串,子組匹配的內(nèi)容'AAAA'不僅會以數(shù)字下標(biāo)保存(這個例子中為1),亦會以字符串下標(biāo)('prefix')保存在$matches里面。九、子組的重復(fù)利用利用下面的方式我們可以重復(fù)利用已經(jīng)在正則表達式中出現(xiàn)的子組:'#(\w+) (?1)#'這個正則表達式會匹配'foo bar'。不過需要注意的是,重用的子組并不會被捕獲。如果想要捕獲重用的子組,則應(yīng)該在子組外面再加上一個括號:'#(\w+) ((?1))#'我們甚至可以通過子組名稱來重復(fù)利用它:'#(?<pattern>\w+) (?&pattern)#'甚至還可以遞歸地調(diào)用子組:'#(\w+, (?1)?)(\w+)#'上面的表達式會匹配'foo, bar, baz, qux'。十、重置分支這一點在PHP官方文檔中已經(jīng)提到了:'#(?:(Sat)ur|(Sun))day#'當(dāng)匹配'Sunday'的時候,我們會發(fā)現(xiàn)在$matches里面下標(biāo)為1的域是空的,這是因為它嘗試過匹配(Sat),由于沒有匹配到內(nèi)容,所以它在$matches里面加入了一個空的匹配項。如果要去掉這個惱人的匹配項,我們需要在匹配不成功的時候重置分支:'#(?|(Sat)ur|(Sun))day#'將原來的冒號改為豎線之后,我們就會發(fā)現(xiàn),原來空的匹配不見了。
子組通過圓括號分隔界定,并且它們可以嵌套。 將一個模式中的一部分標(biāo)記為子組(子模式)主要是來做兩件事情:
將可選分支局部化。比如,模式cat(arcat|erpillar|)匹配 ”cat”, “cataract”, “caterpillar” 中的一個,如果沒有圓括號的話,它匹配的則是 ”cataract”, “erpillar” 以及空字符串。
將子組設(shè)定為捕獲子組(向上面定義的). 當(dāng)整個模式匹配后, 目標(biāo)字符串中匹配子組的部分將會通過?pcre_exec()()?的ovector?參數(shù)回傳給調(diào)用者。 左括號從左至右出現(xiàn)的次序就是對應(yīng)子組的下標(biāo)(從 1 開始), 可以通過這些下標(biāo)數(shù)字來獲取捕獲子模式匹配結(jié)果。
比如,如果字符串 ”the red king” 使用模式((red|white) (king|queen))?進行匹配, 模式匹配到的結(jié)果是 array(“red king”, ”red king”, “red”, “king”) 的形式, 其中第 0 個元素是整個模式匹配的結(jié)果,后面的三個元素依次為三個子組匹配的結(jié)果。 它們的下表分別為 1, 2, 3。
事實上,圓括號履行的兩種功能并不總是有用的。 經(jīng)常我們會有一種需求需要使用子組進行分組, 但又不需要(單獨的)捕獲它們。 在子組定義的左括號后面緊跟字符串 ”?:” 會使得該子組不被單獨捕獲, 并且不會對其后子組序號的計算產(chǎn)生影響。比如, 如果字符串 ”the white queen” 匹配模式?((?:red|white) (king|queen)), 匹配到的結(jié)果會是 array(“white queen”、“white queen”、“white queen”),的和 king|queen 這兩個子組。 捕獲子組序號的最大值是 99, 最大允許擁有的所有子組(包括捕獲的和非捕獲的)的最大數(shù)量為 200。
為了方便簡寫,如果需要在非捕獲子組開始位置設(shè)置選項, 選項字母可以位于 ? 和 : 之間,比如:
(?i:saturday|sunday)(?:(?i)saturday|sunday)
上面兩種寫法實際上是相同的模式。因為可選分支會從左到右嘗試每個分支, 并且選項沒有在子模式結(jié)束前被重置, 并且由于選項的設(shè)置會穿透對后面的其他分支產(chǎn)生影響,因此, 上面的模式都會匹配 ”SUNDAY” 以及 ”Saturday”。
在 PHP 4.3.3 中,可以對子組使用?(?P<name>pattern)?的語法進行命名。 這個子模式將會在匹配結(jié)果中同時以其名稱和順序(數(shù)字下標(biāo))出現(xiàn), PHP 5.2.2中又增加了兩種味子組命名的語法:?(?<name>pattern)?和?(?’name’pattern)。
有時需要多個匹配可以在一個正則表達式中選用子組。 為了讓多個子組可以共用一個后向引用數(shù)字的問題,?(?|?語法允許復(fù)制數(shù)字。 考慮下面的正則表達式匹配Sunday:
(?:(Sat)ur|(Sun))day
這里當(dāng)后向引用 1 空時Sun?存儲在后向引用 2 中. 當(dāng)后向引用 2 不存在的時候?Sat?存儲在后向引用 1中。 使用?(?|修改模式來修復(fù)這個問題:
(?|(Sat)ur|(Sun))day
Black_warlock 提問者
舉報
輕松學(xué)習(xí)PHP中級課程,進行全面了解,用PHP快速開發(fā)網(wǎng)站程序
2 回答正則表達式看不懂啊,大神救命
2 回答正則表達式
1 回答正則表達式
Copyright ? 2025 imooc.com All Rights Reserved | 京ICP備12003892號-11 京公網(wǎng)安備11010802030151號
購課補貼聯(lián)系客服咨詢優(yōu)惠詳情
慕課網(wǎng)APP您的移動學(xué)習(xí)伙伴
掃描二維碼關(guān)注慕課網(wǎng)微信公眾號
2016-08-06
在PCRE正則表達式中,我們可以利用圓括號定義一個子組,我們可以使用preg_match函數(shù)(其他函數(shù)的信息請參考PHP官方API文檔)的第三個參數(shù)捕獲圓括號中匹配的內(nèi)容:
preg_match('#color\h*:\h*([A-Za-z]*)#', 'color: red', $matches);
print_r($matches);
運行的結(jié)果為:
Array
(
??? [0] => color: red
??? [1] => red
)
根據(jù)定義,子組(正則表達式中圓括號)中的內(nèi)容會按照左半邊括號出現(xiàn)的順序,將匹配的內(nèi)容分別存放至$matches數(shù)組中,下標(biāo)從1開始(下標(biāo)0的內(nèi)容為整個匹配的字符串)。
這個特性可以讓我們很方便地從被匹配的字符串中提取我們需要的信息。PCRE中的子組的功能其實非常強大,但是PHP官方的API文檔并沒有對齊作過多的介紹。下面的文章嘗試對PCRE中的子組功能做一個初步的介紹。
二、匹配順序
子組其中一個重要的作用就是用來描述“分支”的匹配,但是如果較短的分支是較長分支的前綴的話,那么較短的分支一定要放在較長的分支后面:
'#(eq|lte|gte|lt|gt)#'
注意,這里的lt必須放在lte的后面,否則的話正則表達式解析器讀到lt時分支就已經(jīng)匹配成功了,那么lte就永遠(yuǎn)不會被匹配到。
三、非捕獲子組
有些時候子組只是用來描述“分支”的匹配的,我們并不想讓最后的$matches里面出現(xiàn)括號里的內(nèi)容,此時可以用非捕獲子組(?:)告訴正則表達式解析器,它不需要被捕獲:
'#(?:https?|ftp)://([A-Za-z\.]+)#'
這樣,URL里面主機名部分就會被存放至$matches數(shù)組下標(biāo)為1的域內(nèi)。而前面的https?|ftp雖然也被打了圓括號,但是由于圓括號中有?:,所以并不會被保存到$matches中。
不過這里僅僅是舉例子,在實際應(yīng)用中,可以調(diào)用parse_url函數(shù)來更好地完成獲取主機名的任務(wù)。
四、前向探測(Lookahead)
前向探測的目的是,在當(dāng)前的點,向后讀入內(nèi)容(對于讀取匹配內(nèi)容的程序來說,它即將讀入的內(nèi)容被稱為“前”;但是對于閱讀者來說,即將讀入的內(nèi)容被
稱為“后”),判斷其是否與子組中的正則表達式相匹配。如果匹配,則繼續(xù)匹配后面的內(nèi)容,否則匹配失敗。雖然前向探測會向后讀入內(nèi)容,但是被讀入的內(nèi)容并
不會被“消耗”掉,也不算做正則表達式匹配的一部分,也就是說,后面的正則表達式依然可以匹配到向后讀入的內(nèi)容。
如果這樣說不太明白,可以看看下面的例子。利用(?=)就可以構(gòu)造一個前向探測:
'#\d*(?= mm)#'
這個正則表達式會匹配如'100 mm'這樣的字符串。由于前向探測的正則表達式mm并不屬于正則表達式的一部分,所以最后整個表達式(注意,不是$matches下標(biāo)為1的域,而是整個表達式,也就是下標(biāo)0)匹配出來的結(jié)果是'100'。
更好的例子是檢查密碼是否符合規(guī)范:
'#^(?=\w{8,20}$)(?=[^A-Z]*[A-Z])(?=[^a-z]*[a-z])(?=\D*\d)(?=[^_]*_).*$#'
這 個正則表達式在最開頭的地方依次使用了5個前向探測子組,分別檢查密碼長度在8至20之間、含有大寫字母、含有小寫字母、含有數(shù)字以及含有下劃線。只有當(dāng) 這五個條件都滿足,正則表達式才會繼續(xù)向下匹配。由于這些子組都不會消耗讀入的內(nèi)容,所以最后我們簡單地使用一個.*就可以獲取整個密碼字符串。
五、前向逆探測(Negative Lookahead)
與前向探測類似,只不過子組中的表達式必須不滿足才行。它的構(gòu)造方法為(?!):
'#\d*(?!\d| mm)#'
這個表達式除了類似于'100 mm'以外其余的類似于'100 cm'這樣的字符串都可以被匹配。注意子組正則表達式里面加了一個\d,因為不加它,當(dāng)讀入'100 mm'的時候,表達式還是會匹配到'10',這是因為'0 mm'不匹配' mm'。
六、后向探測(Lookbehind)
與前向探測類似,后向探測只不過是以當(dāng)前點為準(zhǔn),向前讀入內(nèi)容。后向探測的構(gòu)造方法為(?<=):
'#(?<=EUR ).*#'
這個正則表達式會匹配'EUR 100'這樣的字符串。匹配結(jié)果為'100'而不是'EUR 100',這是因為后向探測是以當(dāng)前點為準(zhǔn),向前讀入內(nèi)容,這也就意味著,當(dāng)開始進行最后.*的匹配時,'EUR '早已被讀過了。
不過這并不意味著后向探測會消耗內(nèi)容,只是因為我們并沒有在正則表達式中匹配'EUR '而已。如果你有興趣,可以嘗試下面的表達式:
'#EUR (?<=EUR)\d*#'
這樣,匹配出來的結(jié)果就是'EUR 100'了。
七、后向逆探測(Negative Lookbehind)
與后向探測類似,只不過子組內(nèi)的表達式必須不匹配。這里就不再舉例了。
八、命名子組
我們可以利用下面的語法命名一個子組:
'#(?P<prefix>A+)C#'
它會匹配類似于'AAAAC'的字符串,子組匹配的內(nèi)容'AAAA'不僅會以數(shù)字下標(biāo)保存(這個例子中為1),亦會以字符串下標(biāo)('prefix')保存在$matches里面。
九、子組的重復(fù)利用
利用下面的方式我們可以重復(fù)利用已經(jīng)在正則表達式中出現(xiàn)的子組:
'#(\w+) (?1)#'
這個正則表達式會匹配'foo bar'。不過需要注意的是,重用的子組并不會被捕獲。如果想要捕獲重用的子組,則應(yīng)該在子組外面再加上一個括號:
'#(\w+) ((?1))#'
我們甚至可以通過子組名稱來重復(fù)利用它:
'#(?<pattern>\w+) (?&pattern)#'
甚至還可以遞歸地調(diào)用子組:
'#(\w+, (?1)?)(\w+)#'
上面的表達式會匹配'foo, bar, baz, qux'。
十、重置分支
這一點在PHP官方文檔中已經(jīng)提到了:
'#(?:(Sat)ur|(Sun))day#'
當(dāng)匹配'Sunday'的時候,我們會發(fā)現(xiàn)在$matches里面下標(biāo)為1的域是空的,這是因為它嘗試過匹配(Sat),由于沒有匹配到內(nèi)容,所以它在$matches里面加入了一個空的匹配項。如果要去掉這個惱人的匹配項,我們需要在匹配不成功的時候重置分支:
'#(?|(Sat)ur|(Sun))day#'
將原來的冒號改為豎線之后,我們就會發(fā)現(xiàn),原來空的匹配不見了。
子組通過圓括號分隔界定,并且它們可以嵌套。 將一個模式中的一部分標(biāo)記為子組(子模式)主要是來做兩件事情:
將可選分支局部化。比如,模式cat(arcat|erpillar|)匹配 ”cat”, “cataract”, “caterpillar” 中的一個,如果沒有圓括號的話,它匹配的則是 ”cataract”, “erpillar” 以及空字符串。
將子組設(shè)定為捕獲子組(向上面定義的). 當(dāng)整個模式匹配后, 目標(biāo)字符串中匹配子組的部分將會通過?pcre_exec()()?的ovector?參數(shù)回傳給調(diào)用者。 左括號從左至右出現(xiàn)的次序就是對應(yīng)子組的下標(biāo)(從 1 開始), 可以通過這些下標(biāo)數(shù)字來獲取捕獲子模式匹配結(jié)果。
比如,如果字符串 ”the red king” 使用模式((red|white) (king|queen))?進行匹配, 模式匹配到的結(jié)果是 array(“red king”, ”red king”, “red”, “king”) 的形式, 其中第 0 個元素是整個模式匹配的結(jié)果,后面的三個元素依次為三個子組匹配的結(jié)果。 它們的下表分別為 1, 2, 3。
事實上,圓括號履行的兩種功能并不總是有用的。 經(jīng)常我們會有一種需求需要使用子組進行分組, 但又不需要(單獨的)捕獲它們。 在子組定義的左括號后面緊跟字符串 ”?:” 會使得該子組不被單獨捕獲, 并且不會對其后子組序號的計算產(chǎn)生影響。比如, 如果字符串 ”the white queen” 匹配模式?((?:red|white) (king|queen)), 匹配到的結(jié)果會是 array(“white queen”、“white queen”、“white queen”),的和 king|queen 這兩個子組。 捕獲子組序號的最大值是 99, 最大允許擁有的所有子組(包括捕獲的和非捕獲的)的最大數(shù)量為 200。
為了方便簡寫,如果需要在非捕獲子組開始位置設(shè)置選項, 選項字母可以位于 ? 和 : 之間,比如:
(?i:saturday|sunday)
(?:(?i)saturday|sunday)
上面兩種寫法實際上是相同的模式。因為可選分支會從左到右嘗試每個分支, 并且選項沒有在子模式結(jié)束前被重置, 并且由于選項的設(shè)置會穿透對后面的其他分支產(chǎn)生影響,因此, 上面的模式都會匹配 ”SUNDAY” 以及 ”Saturday”。
在 PHP 4.3.3 中,可以對子組使用?(?P<name>pattern)?的語法進行命名。 這個子模式將會在匹配結(jié)果中同時以其名稱和順序(數(shù)字下標(biāo))出現(xiàn), PHP 5.2.2中又增加了兩種味子組命名的語法:?(?<name>pattern)?和?(?’name’pattern)。
有時需要多個匹配可以在一個正則表達式中選用子組。 為了讓多個子組可以共用一個后向引用數(shù)字的問題,?(?|?語法允許復(fù)制數(shù)字。 考慮下面的正則表達式匹配Sunday:
(?:(Sat)ur|(Sun))day
這里當(dāng)后向引用 1 空時Sun?存儲在后向引用 2 中. 當(dāng)后向引用 2 不存在的時候?Sat?存儲在后向引用 1中。 使用?(?|修改模式來修復(fù)這個問題:
(?|(Sat)ur|(Sun))day