1. 程式人生 > >前端學PHP之正則表達式基礎語法

前端學PHP之正則表達式基礎語法

版本 得到 則表達式 特性 邊界 包括 報錯 正則表達式基礎 性能提升

前面的話

  正則表達式是用於描述字符排列和匹配模式的一種語法規則。它主要用於字符串的模式分割、匹配、查找及替換操作。在PHP中,正則表達式一般是由正規字符和一些特殊字符(類似於通配符)聯合構成的一個文本模式的程序性描述。正則表達式有三個作用:1、匹配,也常常用於從字符串中析取信息;2、用新文本代替匹配文本;3、將一個字符串拆分為一組更小的信息塊。本文將詳細介紹PHP中的正則表達式基礎語法

  [註意]關於javascript的正則表達式的詳細信息移步至此

歷史

  在PHP中有兩套正則表達式函數庫,兩者功能相似,只是執行效率略有差異:一套是由PCRE(Perl Compatible Regular Expression)庫提供的,使用“preg_”為前綴命名的函數;另一套由POSIX(Portable Operating System Interface of Unix)擴展提供的,使用以“ereg_”為前綴命名的函數

  PCRE來源於Perl語言,而Perl是對字符串操作功能最強大的語言之一,PHP的最初版本就是由Perl開發的產品。PCRE語法支持更多特性,比POSIX語法更強大

  在PHP4之前,主要使用POSIX;而現在,則使用主流的PCRE

  正則表達式作為一個匹配的模式,是由原子(普通字符,例如字符a到z)、特殊字符(元字符,例如*、+和?等)、以及模式修正符三部分組成的文字模式

定界符

  定界符常使用反斜線“/”,如“/apple/”。用戶只要把需要匹配的模式內容放入定界符之間即可。作為定界的字符也不僅僅局限於“/”。除了字母、數字和斜線“\”以外的任何字符都可以作為定界符,像“#”、“|”、“!”等都可以的

/<\/\w+>/              --使用反斜線作為定界符合法
|(\d{3})-\d+|Sm        --使用豎線”|”作為定界符合法
!^(?i)php[34]!         --使用豎線”!”作為定界符合法
/href=‘(.*)’           --非法定界符,缺少結束定界符
1-\d3-\d3-\d4|         --非法定界符,缺少起始定界符

  如果分隔符需要在模式內進行匹配,它必須使用反斜線進行轉義。如果分隔符經常在模式內出現, 一個更好的選擇就是是用其他分隔符來提高可讀性

/http:\/\//
#http://#

元字符

  正則表達式的威力源於它可以在模式中擁有選擇和重復的能力。 一些字符被賦予特殊的涵義,使其不再單純的代表自己,模式中的這種有特殊涵義的編碼字符稱為元字符

  共有兩種不同的元字符:一種是可以在模式中方括號外任何地方使用的,另外一種是需要在方括號內使用的

  【1】在方括號外使用的元字符如下:

技術分享
\ 一般用於轉義字符
^ 斷言目標的開始位置(或在多行模式下是行首)
$ 斷言目標的結束位置(或在多行模式下是行尾)
. 匹配除換行符外的任何字符(默認)
[ 開始字符類定義
] 結束字符類定義
| 開始一個可選分支
( 子組的開始標記
) 子組的結束標記
? 作為量詞,表示 0 次或 1 次匹配。位於量詞後面用於改變量詞的貪婪特性
* 量詞,0 次或多次匹配
+ 量詞,1 次或多次匹配
{ 自定義量詞開始標記
} 自定義量詞結束標記
技術分享

  [註意]在字符類外部,模式中的句點.匹配目標字符串中的任意字符,包括非打印字符, 但是默認不包括換行符。如果PCRE_DOTALL被設置,句點就會匹配換行符

  【2】模式中方括號內的部分稱為“字符類”。 在一個字符類中僅有以下可用元字符:

\ 轉義字符
^ 僅在作為第一個字符(方括號內)時,表明字符類取反
- 標記字符範圍

反斜線

  反斜線有多種用法。首先,如果緊接著是一個非字母數字字符,表明取消該字符所代表的特殊涵義。這種將反斜線作為轉義字符的用法在字符類內部和外部都可用

  比如,如果希望匹配一個 "*" 字符,需要在模式中寫為"\*"。這適用於一個字符在不進行轉義會有特殊含義的情況下。但是,對於非數字字母的字符,總是在需要其進行原文匹配的時候在它前面增加一個反斜線,來聲明它代表自己,這是安全的。如果要匹配一個反斜線,那麽在模式中使用 ”\\”

  反斜線在單引號字符串和雙引號字符串中都有特殊含義,因此要匹配一個反斜線,模式中必須寫為 ”\\\\”

  [註意] “/\\/”, 首先它作為字符串,反斜線會進行轉義, 那麽轉義後的結果是/\/,這個才是正則表達式引擎拿到的模式,而正則表達式引擎也認為\是轉義標記,它會將分隔符/進行轉義,從而得到的是一個錯誤,因此,需要4個反斜線才可以匹配一個反斜線

  反斜線的第二種用途提供了一種對非打印字符進行可見編碼的控制手段。除了二進制的0 會終結一個模式外,並不會嚴格的限制非打印字符(自身)的出現,但是當一個模式以文本編輯器的方式編輯準備的時候,使用下面的轉義序列相比使用二進制字符會更加容易

技術分享
\a      響鈴字符(十六進制 07)
\cx     "control-x",x 是任意字符
\e      轉義 (十六進制 1B)
\f      換頁 (十六進制 0C)
\n      換行 (十六進制 0A)
\p{xx}  一個符合 xx 屬性的字符
\P{xx}  一個不符合xx屬性的字符
\r      回車 (十六進制 0D)
\t      水平制表符 (十六進制 09)
\xhh    hh十六進制編碼的字符
\ddd    ddd八進制編碼的字符,或者後向引用
技術分享

字符類

技術分享
\d  任意十進制數字
\D  任意非十進制數字
\h  任意水平空白字符
\H  任意非水平空白字符
\s  任意空白字符
\S  任意非空白字符
\v  任意垂直空白字符
\V  任意非垂直空白字符
\w  任意單詞字符
\W  任意非單詞字符
技術分享

  [註意]單詞字符指的是任意字母、數字、下劃線。也就是說任意可以組成perl單詞的字符

  反斜線的第四種用法是一些簡單的斷言。一個斷言指定一個必須在特定位置匹配的條件, 它們不會從目標字符串中消耗任何字符。反斜線斷言包括:

技術分享
\b  單詞邊界
\B  非單詞邊界
\A  目標的開始位置(獨立於多行模式)
\Z  目標的結束位置或結束處的換行符(獨立於多行模式)
\z  目標的結束位置(獨立於多行模式)
\G  在目標中首次匹配位置
技術分享

  [註意]\A、\Z、\z斷言不同於傳統的^和$,因為他們永遠匹配目標字符串的開始和結尾,而不會受模式修飾符的限制

  在一個字符類外面,在默認匹配模式下,^是一個斷言當前匹配點位於目標字符串開始處的斷言。在一個字符類內部,^表明這個字符類中描述的字符取反

  ^並不一定要是模式的第一個字符,但是如果處於某個可選分支時,它應該是該分支的首字符。如果所有選擇分支都以 ^ 開頭,這就是說,如果模式限制為只匹配目標的開頭, 它被稱為是一個 ”緊固” 模式

  美元符號是用於斷言當前匹配點位於目標字符串末尾,或當目標字符串以換行符結尾時當前匹配點位於該換行符位置(默認情況)。$不一定要作為模式的最後一個字符,但是如果它在某個可選分支中時,就應該位於該分支的末尾。美元符號在字符類中沒有特殊的意義

  美元符號的意義可以通過在編譯或匹配時設置PCRE_DOLLAR_ENDONLY 改變為只匹配字符串末尾。 這不會影響\Z斷言的行為

  ^ 和美元符號字符的意義在 PCRE_MULTILINE 選項被設置時會發生變化。當在這種情況下時, 它們匹配每一個換行符後面的和前面的字符,另外,也會匹配目標字符串的開始和結束。比如,模式 /^abc美元符號/ 在多行模式下會成功匹配目標字符串 ”def\nabc”,而正常情況下不會。因此,由於所有的可選分支都以 ^ 開始,在單行模式下這成為緊固模式,然而在多行模式下,這是非緊固的。PCRE_DOLLAR_ENDONLY選項在PCRE_MULTILINE 設置後失效

美元符號  $

字符類

  左方括號開始一個字符類的描述,並以方中括號結束。單獨的一個右方括號沒有特殊含義。如果一個右方括號需要作為一個字符類中的成員,那麽可以將它寫在字符類的首字符處(如果使用了^取反,那麽是第二個)或者使用轉義符

  一個字符類在目標字符串中匹配一個單獨的字符;該字符必須是字符類中定義的字符集合的一個,除非使用^對字符類取反。如果^需要作為一個字符類的成員,確保它不是該字符類的首字符,或者對其進行轉義即可

  例如,字符類[aeiou]匹配所有的小寫元音字母,而[^aeiou]匹配所有非元音字母的字符。註意:^只是一個通過枚舉指定那些不存在字符類之中的字符的便利符號。而不是斷言,它仍然會從目標字符串中消耗一個字符,並且如果當前匹配點在目標字符串末尾,匹配將會失敗

  當大小寫無關匹配被設置後,任意字符類都同時代表大小寫兩種版本,因此對於例子, 一個大小寫不敏感的[aeiou]同時匹配"A"和"a",並且大小寫不敏感的[^aeiou]同時不匹配"A"

  換行符在字符類中沒有任何特殊涵義,與 PCRE_DOTALL 或 PCRE_MULTILINE 選項都無關。 一個字符類比如 [^a] 始終會匹配換行符。

  在字符類中,一個中劃線(減號 -)可以用於指定從一個字符到另一個字符的範圍。 比如,[d-m]匹配d到m之間的所有字符,這個集合是閉合的。如果中劃線自身要在一個字符類中描述,它必須被轉移或者出現在一個不會被解釋為一個範圍的位置,典型的比如字符類開始或結束位置

  在一個字符範圍描述後面不能使用右中括號。 比如一個模式 [W-]46]被解釋為一個包含 W 和 - 的字符類,後面緊跟字符串 ”46]”,因此它可以匹配 ”W46]” 或 ”-46]”。然而,如果中括號是經過轉義的,它將會被解釋為範圍的終點,因此 [W-\]46]就會被解釋為一個單獨的包含 W 至 ] 範圍內所有字符以及 4、6 的字符類。 8進制或16進制描述的中括號同樣可以用於作為範圍的終點。

  範圍操作以ASCII整理排序。它們可以用於為字符指定數值,比如 [\000-\037]。 如果在大小寫不敏感匹配模式下使用一個包含字母的範圍,則同時匹配它的大小寫形式。 比如 [W-c] 在不區分大小寫匹配時等價於 [][\^_`wxyzabc],並且,如果使用了 ”fr”(法國) 的地域設置字符表時,[\xc8-xcb] 將會在所有模式下匹配重音E字符

  字符類\d、\D、 \s、\S、\w 和 \W 也可以出現在一個字符類中,用以將其匹配的字符類加入到新的自定義字符類中。比如,[\dABCDEF]匹配任意合法的16進制數。用^可以很方便的制定嚴格的字符類,比如[^\W_]匹配任何字母或數字,但不匹配下劃線。

  所有非字母數字字符除了\、-、 ^(在起始位置)以及結束的]在字符類中都是非特殊字符,沒有轉義也不會有危害。模式結束符在表達式中總是特殊字符,必須進行轉義

可選路徑

  豎線字符(|)用於分離模式中的可選路徑。 比如模式gilbert|Sullivan匹配 ”gilbert” 或者 ”sullivan”。 豎線可以在模式中出現任意多個,並且允許有空的可選路徑(匹配空字符串)。 匹配的處理從左到右嘗試每一個可選路徑,並且使用第一個成功匹配的。 如果可選路徑在子組中,則”成功匹配”表示同時匹配了子模式中的分支以及主模式中的其他部分

模式修正符

  模式修正符在正則表達式定界符之外使用,一般地在最後一個斜線之後。模式修正符可以調整正則表達式的解釋,擴展了正則表達式在匹配、替換等操作時的某些功能,而且模式修正符也可以組合使用,更增強了正則表達式的處理能力

  模式修正符對編寫簡潔而短小的表達式大有幫助,下面列出了一些常用的模式修正符及其功能說明

i  在模式進行匹配時不區分大小寫

m  將字符串視為多行。默認的正則開始^和結束$將目標字符串作為單一的一行字符。如果使用m修飾符,那麽開始和結束將會指向字符串的每一行

s  模式中的點字符.匹配所有的字符,包括換行符

x  模式中的空白忽略不計,除非它已經被轉義

e  只用在preg_replace()函數中,在替換字符串中對逆向引用做正常的替換,將其作為PHP代碼求值,並用其結果來替換所搜索的字符串

U  本修正符反轉了匹配數量的值使其不是默認的重復,而變成在後面跟上?才變得重復,這和Perl不兼容。也可以通過在模式之中設定U修正符或者在數量符之後跟一個問號?來開啟此選項

D  模式中的美元符號僅匹配目標字符串的結尾。沒有些選項時,如果最後一個字符是換行符,美元符號也會匹配此字符之前。如果設定了m修正符則忽略此選項

子組

  子組(子模式)通過圓括號分隔界定,並且它們可以嵌套。將一個模式中的一部分標記為子組(子模式)主要是來做兩件事情:

  1、將可選分支局部化。比如,模式cat(arcat|erpillar|)匹配 ”cat”、“cataract”、“caterpillar” 中的一個,如果沒有圓括號的話,它匹配的則是 ”cataract”、“erpillar” 以及空字符串

  2、將子組設定為捕獲子組。當整個模式匹配後,目標字符串中匹配子組的部分將會通過 pcre_exec()()的ovector參數回傳給調用者。左括號從左至右出現的次序就是對應子組的下標(從 1 開始), 可以通過這些下標數字來獲取捕獲子模式匹配結果。

  比如,如果字符串 "the red king" 使用模式((red|white) (king|queen)) 進行匹配, 模式匹配到的結果是 array("red king", "red king", "red", "king") 的形式, 其中第 0 個元素是整個模式匹配的結果,後面的三個元素依次為三個子組匹配的結果。 它們的下標分別為 1、2、3

  事實上,圓括號履行的兩種功能並不總是有用的。經常我們會有一種需求需要使用子組進行分組, 但又不需要(單獨的)捕獲它們。 在子組定義的左括號後面緊跟字符串 "?:" 會使得該子組不被單獨捕獲,並且不會對其後子組序號的計算產生影響。比如,如果字符串 "the white queen" 匹配模式 ((?:red|white) (king|queen)),匹配到的結果會是 array("white queen"、"white queen"、"white queen")和 king|queen 這兩個子組。 捕獲子組序號的最大值是99,最大允許擁有的所有子組(包括捕獲的和非捕獲的)的最大數量為 200。

  為了方便簡寫,如果需要在非捕獲子組開始位置設置選項, 選項字母可以位於 ? 和 : 之間,比如:

(?i:saturday|sunday)
(?:(?i)saturday|sunday)

  上面兩種寫法實際上是相同的模式。因為可選分支會從左到右嘗試每個分支, 並且選項沒有在子模式結束前被重置,並且由於選項的設置會穿透對後面的其他分支產生影響,因此,上面的模式都會匹配 "SUNDAY" 以及 "Saturday"

  在 PHP 4.3.3 中,可以對子組使用 (?P<name>pattern)的語法進行命名。這個子模式將會在匹配結果中同時以其名稱和順序(數字下標)出現, PHP 5.2.2中又增加了兩種味子組命名的語法:(?<name>pattern) 和 (?’name’pattern)

  有時需要多個匹配可以在一個正則表達式中選用子組。 為了讓多個子組可以共用一個後向引用數字的問題, (?| 語法允許復制數字

  考慮下面的正則表達式匹配Sunday:

(?:(Sat)ur|(Sun))day

  這裏當後向引用 1 空時Sun 存儲在後向引用 2 中。當後向引用 2 不存在的時候 Sat 存儲在後向引用 1中。 使用 (?|修改模式來修復這個問題:

(?|(Sat)ur|(Sun))day

  使用這個模式, Sun和Sat都會被存儲到後向引用1中

量詞

  重復次數是通過量詞指定的,可以緊跟在下面元素之後:單獨的字符,可以是經過轉義的;元字符;字符類;後向引用;子組(除非它是一個斷言)

  一般的重復量詞指定了一個最小數值和一個最大數值的匹配次數,通過花括號包裹兩個數字,兩個數字之間用逗號隔開的語法定義。兩個數值都必須小於65536,並且第一個數字必須小於等於第二個

  比如:z{2,4}匹配 ”zz”,“zzz”,“zzzz”。單個的右花括號不是特殊字符。如果第二個數字被省略,但是逗號仍然存在,就代表沒有上限;如果第二個數字和逗號都被省略,那麽這個量詞就限定的是一個確定次數的匹配。比如:[aeiou]{3,}匹配至少三個連續的元音字母,但是同時也可以匹配更多,而\d{8} 則只能匹配8個數字。左花括號出現在不允許使用量詞的位置或者與量詞語法不匹配時,被認為是一個普通字符,對它自身進行原文匹配。比如,{,6}就不是一個量詞,會按照原文匹配四個字符 ”{,6}”

  量詞{0}是被授權的,它會導致的行為是認為前面的項和量詞不存在

  為了方便(以及歷史的兼容性),最常用的三個量詞都有單字符縮寫

*    等價於 {0,}
+    等價於 {1,}
?    等價於 {0,1}

  可以通過一個不匹配任何字符的子模式後面緊跟一個匹配0或多個字符的量詞來構造一個沒有上限的無限循環。比如:(a?)*

  默認情況下,量詞都是“貪婪”的,也就是說,它們會在不導致模式匹配失敗的前提下,盡可能多的匹配字符(直到最大允許的匹配次數)。 這種問題的典型示例就是嘗試匹配C語言的註釋。出現在/*和*/之間的所有內容都被認為是註釋。在註釋中間,可以允許出現單獨的 * 和 /

  對C註釋匹配的一個嘗試是使用模式 /\*.*\*/, 假設將此模式應用在字符串 ”/* first comment*/ not comment /*second comment*/” 它會匹配到錯誤的結果,也就是整個字符串, 這是因為量詞的貪婪性導致的,它會嘗試盡可能多的匹配字符。

  然而,如果一個量詞緊跟著一個 ?(問號) 標記,它就會成為懶惰(非貪婪)模式, 它不再盡可能多的匹配,而是盡可能少的匹配。 因此模式 /\*.*?\*/ 在C的註釋匹配上將會正確的執行。各個量詞自身的意義並不會改變,而是由於加入了?使其首選的匹配次數發生改變。不要將?的這個用法和它作為量詞的用法混淆。因為它又兩種用法,因此有時它會出現量詞,比如\d??\d會更傾向於匹配一個數字,但同時如果為了達到整個模式匹配的目的,它也可以接受兩個數字的匹配。譯註: 以模式 \w\d??\d\w為例,對於字符串”a33a”,雖然\d??是非貪婪的,但由於如果使用貪婪會導致整個模式不匹配。所以,最終它選擇的仍然是匹配到一個數字

  如果 PCRE_UNGREEDY 選項被設置(一個在 perl 中不可用的選項),那麽量詞默認情況下就是非貪婪的了。但是,單個的量詞可以通過緊跟一個?來使其成為貪婪的。換句話說,PCRE_UNGREEDY這個選項逆轉了貪婪的默認行為

  量詞後面緊跟一個“+”是“占有”性。它會吃掉盡可能多的字符,並且不關註後面的其他模式,比如,.*abc 匹配 ”aabc”,但是 .*+abc 不會匹配,因為.*+會吃掉整個字符串,從而導致後面剩余的模式得不到匹配。自PHP 4.3.3 起,可以使用占有符 (+) 修飾量詞來達到提升速度的目的

  當一個子組受最小數量大於1或有一個最大數量限制的量詞修飾時,按照最小或最大的數量的比例需要更多的存儲用於編譯模式

  如果一個模式以 .* 或 .{0,} 開始並且PCRE_DOTALL選項開啟(等價於 perl 的/s),也就是允許.匹配換行符,那麽模式會隱式的緊固,因為不管怎麽樣,接下來都會對目標字符串中的每個字符位置進行嘗試,因此在第一次之後,在任何位置都不會有一個對所有匹配重試的點。PCRE會想對待\A一樣處理這個模式。 在我們已知目標字符串沒有包含換行符的情況下,當模式以.*開始的時候我們為了獲得這個優化,值得設置 PCRE_DOTALL,或者選擇使用 ^ 明確指明錨定

  當一個捕獲子組時重復的時,捕獲到的該子組的結果是最後一次叠代捕獲的值。比如,(tweedle[dume]{3}\s*)+匹配字符串“tweedledum tweedledee”,得到的的子組捕獲結果是”tweedledee”。然而,如果是嵌套的捕獲子組,相應的捕獲值可能會被設置到之前的叠代中。比如,/(a|(b))+/ 匹配字符串 ”aba”, 第二個捕獲子組得到的結果會是 ”b”

後向引用

  在一個字符類外面,反斜線緊跟一個大於0(可能還有一位數)的數字就是一個到模式中之前出現的某個捕獲組的後向引用

  如果緊跟反斜線的數字小於10,它總是一個後向引用,並且如果在模式中沒有這麽多的捕獲組會引發一個錯誤。換一種說法,被引用的括號不能少於被引用的小於10的數量

  一個後向引用會直接匹配被引用捕獲組在目標字符串中實際捕獲到的內容,而不是匹配子組模式的內容。因此,模式(sens|respons)e and \1ibility將會匹配 ”sense and sensibility” 和 ”response and responsibility”, 而不會匹配 ”sense and responsibility”

  如果在後向引用時被強制進行了大小寫敏感匹配, 比如 ((?i)rah)\s+\1 匹配 ”rah rah”和”RAH RAH”,但是不會匹配 ”RAH rah”, 即使原始捕獲子組自身是不區分大小寫的

  可能會有超過一個的後向引用引用相同的子組。一個子組可能並不會真正的用於特定的匹配,此時,任何對這個子組的後向引用也都會失敗。 比如,模式 (a|(bc))\2 總是在匹配 ”a” 開頭而不是 ”bc” 開頭的字符串時失敗。因為可能會有多達99個後向引用,所有緊跟反斜線後的數字都可能是一個潛在的後向引用計數。如果模式在後向引用之後緊接著還是一個數值字符,那麽必須使用一些分隔符用於終結後向引用語法。如果PCRE_EXTENDED選項被設置了,可以使用空格來做。其他情況下可以使用一個空的註釋

  如果一個後向引用出現在它所引用的子組內部,它的匹配就會失敗。比如,(a\1)就不會得到任何匹配。然而這種引用可以用於內部的子模式重復。比如,模式(a|b\1)+會匹配任意數量的“a”組成的字符串以及“aba”,“ababba”等等。在每次子模式的叠代過程中, 後向引用匹配上一次叠代時這個子組匹配到的字符串。為了做這種工作,模式必須滿足這樣一個條件,模式在第一次叠代的時候,必須能夠保證不需要匹配後向引用。這種條件可以像上面的例子用可選路徑來實現,也可以通過使用最小值為 0 的量詞修飾後向引用的方式來完成

  在 PHP 5.2.2之後,\g 轉義序列可以用於子模式的絕對和相對引用。這個轉義序列必須緊跟一個無符號數字或一個負數,可以選擇性的使用括號對數字進行包裹。序列\1,\g1,\g{1}之間是同義詞關系。這種用法可以消除使用反斜線緊跟數值描述反向引用時候產生的歧義。這種轉義序列有利於區分後向引用和八進制數字字符,也使得後向引用後面緊跟一個原文匹配數字變的更明了,比如 \g{2}1

  \g 轉義序列緊跟一個負數代表一個相對的後向引用。比如:(foo)(bar)\g{-1}可以匹配字符串”foobarbar”, (foo)(bar)\g{2} 可以匹配 ”foobarfoo”。 這在長的模式中作為一個可選方案, 用來保持對之前一個特定子組的引用的子組序號的追蹤

  後向引用也支持使用子組名稱的語法方式描述, 比如 (?P=name) 或者 PHP 5.2.2 開始可以實用\k<name> 或 \k’name’。 另外在 PHP 5.2.4 中加入了對\k{name} 和 \g{name} 的支持

斷言

  一個斷言就是一個對當前匹配位置之前或之後的字符的測試,它不會實際消耗任何字符。簡單的斷言代碼有\b、\B、 \A、 \Z、\z、 ^、$ 等等。 更加復雜的斷言以子組的方式編碼。 它有兩種類型:前瞻斷言(從當前位置向前測試)和後瞻斷言(從當前位置向後測試)

  一個斷言子組的匹配還是通過普通方式進行的,不同在於它不會導致當前的匹配點發生改變。前瞻斷言中的正面斷言(斷言此匹配為真)以“(?=”開始,消極斷言以“(?!”開頭。比如,\w+(?=;)匹配一個單詞緊跟著一個分號但是匹配結果不會包含分號,foo(?!bar)匹配所有後面沒有緊跟“bar”的“foo”字符串。註意一個類似的模式(?!foo)bar,它不能用於查找之前出現所有不是“foo”的“bar”匹配,它會查找到任意的“bar”出現的情況,因為 (?!foo)這個斷言在接下來三個字符時“bar”的時候是永遠都TRUE的。前瞻斷言需要達到的就是這樣的效果

  後瞻斷言中的正面斷言以“(?<=”開始, 消極斷言以“(?<!”開始。比如,(?<!foo)bar用於查找任何前面不是“foo”的“bar”。後瞻斷言的內容被嚴格限制為只能用於匹配定長字符串。但是,如果有多個可選分支,它們不需要擁有相同的長度。比如 (?<=bullock|donkey)是允許的, 但是 (?<!dogs?|cats?)將會引發一個編譯期的錯誤。在最上級分支可以匹配不同長度的字符串是允許的。相比較於perl5.005而言,它會要求多個分支使用相同長度的字符串匹配。(?<=ab(c|de))這樣的斷言是不允許的,因為它單個的頂級分支可以匹配兩個不同的長度,但是它可以接受使用兩個頂級分支的寫法 (?<=abc|abde) 這樣的斷言實現,對於每個可選分支,暫時將當前位置移動到嘗試匹配的當前位置之前的固定寬度處。 如果在當前沒有足夠的字符就視為匹配失敗。後瞻斷言與一次性子組結合使用可以用來匹配字符串結尾; 一個例子就是在一次性子組上給出字符串結尾

  多個斷言(任意順序)可以同時出現。 比如 (?<=\d{3})(?< !999)foo 匹配前面有三個數字但不是“999”的字符串“foo”。註意,每個斷言獨立應用到對目標字符串該點的匹配。首先它會檢查前面的三位都是數字,然後檢查這三位不是“999”。這個模式不能匹配“foo”前面有三位數字然後緊跟3位非999共6個字符的字符串,比如,它不匹配“123abcfoo”。匹配“123abcfoo” 這個字符串的模式可以是(?<=\d{3}…)(?< !999)foo。這種情況下,第一個斷言查看(當前匹配點)前面的6個字符,檢查前三個是數字,然後第二個斷言檢查(當前匹配點)前三個字符不是“999”

  斷言可以以任意復雜度嵌套。 比如 (?<=(?<!foo)bar)baz 匹配前面有“bar”但是“bar”前面沒有“foo” 的“baz”。另外一個模式 (?<=\d{3}…(?< !999))foo則匹配前面有三個數字字符緊跟3個不是999的任意字符的 ”foo”

  斷言子組時非捕獲子組,並且不能用量詞修飾,因為對同一件事做多次斷言是沒有意義的。如果所有的斷言都包含一個捕獲子組,那麽為了在整個模式中捕獲子組計數的目的,它們都會被計算在內。然而, 子字符串的捕獲僅可以用於正面斷言,因為對於消極的斷言是沒有意義的。

  將斷言計算在內,可以擁有的最大子組數量是 200 個

一次性子組

  對於同時有最大值和最小值量詞限制的重復項,在匹配失敗後,緊接著會以另外一個重復次數重新評估是否能使模式匹配。當模式的作者明確知道執行上沒有問題時,通過改變匹配的行為或者使其更早的匹配失敗以阻止這種行為是很有用的

  考慮一個例子,模式\d+foo應用到目標行123456bar時:

  在匹配了6個數字後匹配”foo”時失敗,通常的行為時匹配器嘗試使\d+只匹配5個數字,只匹配4個數字,在最終失敗之前依次進行嘗試。一次性子組提供了一種特殊的意義,當模式的一部分得到匹配後,不再對其進行重新評估,因此匹配器在第一次匹配“foo”失敗後就能立刻失敗。語法符號是另外一種特殊的括號,以(?>開始,比如(?>\d+)bar

  這種括號對模式的一部分提供了”鎖定”,當它包含一個匹配之後,會阻止未來模式失敗後對它內部的後向回溯。後向回溯在這裏失效,其他工作照常進行

  換一種說法,如果在目標字符串中當前匹配點是錨點,這種類型的子組匹配的字符串等同於一個獨立的模式匹配。

  一次性子組不是捕獲子組。如上面的例子,簡單而言,就是盡其所能吃掉盡可能多的匹配字符。因此,盡管\d+和\d+?都會調整要匹配的數字的個數以便模式的其他部分匹配,(?>\d+)卻僅能匹配整個數字序列

  這個(語法)結構可以包含任意復雜度的字符,也可以嵌套

  一次性子組可以和後瞻斷言結合使用來指定在目標字符串末尾的有效匹配。考慮當一個簡單的模式比如abcd應用到一個不匹配的長字符串上。由於匹配時從左到右處理的,PCRE會從目標中查找每一個”a”然後查看是否緊接著會匹配模式的剩余部分。如果模式是.?abcd應用到一個不匹配的長字符串上。由於匹配時從左到右處理的,PCRE會從目標中查找每一個”a”然後查看是否緊接著會匹配模式的剩余部分。如果模式是.?abcd,那麽初始的.*將首先匹配整個字符串,但是當它失敗後(因為緊接著不是”a”),它會回溯所有的匹配,依次吐出最後1個字符,倒數第2個字符等等。從右向左查找整個字符串中的”a”,因此,我們不能很好的退出。然而,如果模式寫作^(?>.*)(?<=abcd)那麽它就不會回溯.*這一部分,它僅僅用於匹配整個字符串。後瞻斷言對字符串末尾的後四個字符做了一個測試。如果它失敗,匹配立即失敗。對於長字符串,這個模式將會帶來顯著的處理時間上的性能提升。

  當一個模式中包含一個子組自己可以無限重復並且內部有無限重復元素時,使用一次性子組是避免一些失敗匹配消耗大量時間的唯一途徑。模式(\D+|<\d+>)*[!?]匹配一個不限制數目的非數字字符或由<>閉合的數字字符緊跟著!或?。當它匹配的時候,運行時快速的。然而,如果它應用到”aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa”上將會在報告錯誤之前消耗很多時間。這是因為字符串可以用於兩種重復規則,並且需要為兩種重復規則都分配進行嘗試。(示例的結尾使用[!?]而不是單個的字符,是因為PCRE和perl都會對模式最後是一個單獨字符時的快速報錯有優化。它們會記錄最後需要匹配的單個字符,當它們沒有出現在字符串中時快速報錯。)如果模式修改為((?>\D+)|<\d+>)*[!?]就會快速得到報錯

條件子組

  可以使匹配器根據一個斷言的結果,或者之前的一個捕獲子組是否匹配來條件式的匹配一個子組或者在兩個可選子組中選擇。條件子組的兩種語法如下:

(?(condition)yes-pattern)
(?(condition)yes-pattern|no-pattern)

  如果條件滿足,使用yes-pattern,其他情況使用no-pattern(如果指定了)。如果有超過2個的可選子組,會產生給一個編譯期錯誤

  條件一共有兩種。如果在(condition)的括號內是數字組成的文本,條件在該數字代表的(之前的)子組得到匹配時滿足(即使用yes-pattern)。考慮下面的模式,為了使其易於閱讀其中增加了一些空白字符(查看PCRE_EXTENDED選項)並且將其分為三個部分:()?[()]+(?(1))?[()]+(?(1))

  模式的第一部分匹配一個可選的左括號,並且如果該字符出現,設置其為第一個子組的捕獲子串。第二部分匹配一個或多個非括號字符。第三部分是一個條件子組,它會測試第一個子組是否匹配,如果匹配到了,也就是說目標字符串以左括號開始,條件為TRUE,那麽使用yes-pattern也就是這裏需要匹配一個右括號。其他情況下,既然no-pattern沒有出現,這個子組就不匹配任何東西。換句話說,這個模式匹配一個沒有括號的或者閉合括號包裹的字符序列。

  如果條件式字符串(R),它在得到對模式或子模式的遞歸調用時滿足。在”最上級”,條件總是false。

  如果條件不是數字序列或(R),它就必須是一個斷言。這裏的斷言可以使任意的,積極,消極,正向,後向都是可以的。考慮這個模式,同樣為了方便閱讀, 增加了一些空白字符,並且在第二行有兩個可選路徑

(?(?=[^a-z]*[a-z])
\d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} )

  條件式一個正向積極斷言,匹配一個可選的非小寫字母字符序列緊接著一個小寫字母。 換一種說法,它測試目標中至少出現一個小寫字母,如果小寫字母發現, 目標匹配第一個可選分支,其他情況下它匹配第二個分支。 這個模式匹配兩種格式的字符串:dd-aaa-dd 或 dd-dd-dd。aaa 代表小寫字母, dd 是數字

註釋

  字符序列(?#標記開始一個註釋直到遇到一個右括號。不允許嵌套括號。註釋中的字符不會作為模式的一部分參與匹配

  如果設置了 PCRE_EXTENDED 選項, 一個字符類外部的未轉義的 # 字符就代表本行剩余部分為註釋

遞歸模式

  考慮匹配圓括號內字符串的問題,允許無限嵌套括號。如果不使用遞歸,最好的方式是使用一個模式匹配固定深度的嵌套。它不能處理任意深度的嵌套。perl5.6提供了一個實驗性的功能允許正則表達式遞歸。特殊項(?R)提供了遞歸的這種特殊用法。這個PCRE模式解決了圓括號問題(假設PCRE_EXTENDED選項被設置了,因此空白字符被忽略):((?>[()]+)|(?R))?((?>[()]+)|(?R))?。

  首先,它匹配一個左括號。然後它匹配任意數量的非括號字符序列或一個模式自身的遞歸匹配(比如,一個正確的括號子串),最終,匹配一個右括號。

  這個例子模式包含無限重復的嵌套,因此使用了一次性子組匹配非括號字符,這在模式應用到模式不匹配的字符串時非常重要。比如,當它應用到(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa()時就會很快的產生”不匹配”結果。然而,如果不使用一次性子組,這個匹配將會運行很長時間,因為有很多途徑讓+和*重復限定分隔目標字符串,並且在報告失敗之前需要測試所有路徑。

  所有捕獲子組最終被設置的捕獲值都是從遞歸最外層子模式捕獲的值。如果上面的模式匹配(ab(cd)ef),捕獲子組最終被設置的值為”ef”,即頂級得到的最後一個值。如果增加了額外的括號,(((?>[()]+)|(?R))?)(((?>[()]+)|(?R))?),捕獲到的字符串就是頂層括號的匹配內容”ab(cd)ef”。如果在模式中有超過15個捕獲括號,PCRE在遞歸期間就會使用pcre_malloc分配額外的內存來存儲數據,隨後通過pcre_free釋放他們。如果沒有內存可被分配,它就僅保存前15個捕獲括號,在遞歸內部無法給出內存不夠用的錯誤。

  從PHP4.3.3開始,(?1)、(?2)等可以用於遞歸子組。這同樣可以用於命名子組:(?P>name)或(?P&name)。

  如果遞歸子組語法在它提到的子組括號外部使用(無論是子組數字序號還是子組名稱),這個操作就相當於程序設計語言中的子程序。前面一些有一個例子指出模式(sens|respons)eand\1ibility匹配 ”sense and responsibility” 和 ”response and responsibility”,但是不匹配 ”sense and responsibility”。如果用模式 (sens|respons)e and (?1)ibility 替代, 它會像匹配那兩個字符串一樣匹配 ”sense and responsibility”。 這種引用方式意義是緊接著匹配引用的子模式

  目標字符串的最大長度是 int 型變量可以存儲的最大正整數。然而, PCRE 使用遞歸處理子組和無限重復。 這就是說對於某些模式可用的棧空間可能會受目標字符串限制

性能

  模式中一些項可能比其他一些更加高效。比如使用[aeiou]這樣的字符類會比可選路徑(a|e|i|o|u)高效。一般而言,用盡可能簡單的構造描述需求是最高效的

  當一個模式以.*開始並且設置了PCRE_DOTALL選項時,模式通過PCRE隱式錨定,因為它可以匹配字符串的開始。然而,如果PCRE_DOTALL沒有設置,PCRE不能做這個優化,因為.元字符不能匹配換行符,如果目標字符串包含換行符,模式可能會從一個換行符後面開始匹配,而不是最開始位置。比如,模式(.*)second匹配目標字符串”first\nandsecond”(\n是一個換行符)第一個捕獲子組結果是”and”。為了這樣做,PCRE嘗試從目標字符串中每個換行符後開始匹配

  如果使用模式匹配沒有換行符的目標字符串,可以通過設置PCRE_DOTALL或以^.*開始的模式明確指示錨定以獲取最佳性能。這樣節省了PCRE沿目標字符串掃描查找換行符重新開始的時間。

  小心模式中的無限重復嵌套。這在應用到不匹配字符串時可能會導致運行時間很長。考慮模式片段(a+)*

  對於一些簡單的情況的優化是像(a+)*b這樣緊接著使用原文字符串.。在著手正式匹配工作之前,PCRE檢查目標字符串後面是否有”b”字符,如果沒有就立即失敗。然而當緊接著沒有原文字符的時候這個優化是不可用的。你可以比較觀察(a+)*\d和上面模式的行為差異。前者在應用到整行的”a”組成的字符串時幾乎是立即報告失敗,而後者在目標字符串長於20個字符時,時間消耗就相當可觀

前端學PHP之正則表達式基礎語法