什麼是正則表達式在電腦上我們經常會使用(通配符)找出我們需要的文件,例如:*.doc ,這裡的 * 代表匹配零個或多個字元。正則表達式也是用來進行文本匹配的工具,只不過它更加強悍。引用 PHP 手冊里的一句話:正則表達式是一個從左到右匹配目標字元串的模式,大多數字元自身就代表一個匹配 它們自身的模式...
什麼是正則表達式
在電腦上我們經常會使用(通配符)找出我們需要的文件,例如:*.doc
,這裡的 *
代表匹配零個或多個字元。正則表達式也是用來進行文本匹配的工具,只不過它更加強悍。引用 PHP 手冊里的一句話:正則表達式是一個從左到右匹配目標字元串的模式,大多數字元自身就代表一個匹配 它們自身的模式。
下麵給出幾個簡單例子,使對正則表達式有個初步的理解。
hi //匹配英文字元(忽略大小寫) hi , HI , Hi , hI
\bhi\b //匹配英文單詞 hi '\b'是正則里的一特殊字元(一種斷言),表示單詞邊界
\bhi\b.*\bLucy\b //匹配如:'hi my name is Lucy' '.' 表示匹配除換行符以外的任意字元 '*' 是量詞,表示重覆零次或更多次
0\d{2}-\d{8} //匹配如: 020-12345678 '\d' 匹配一個數字(0-9) '{n}' 重覆n次,如{2} {8}
上面例子中的 \b
, .
, *
, \d
, {2}
都有特殊含義,在下文會有說明。
PHP 中正則語法
1.簡介
在 PHP 里支持兩種正則分別是 POSIX 和 PCRE 。自 PHP 5.3.0起,POSIX 正則表達式擴展被廢棄。所以下文討論的都是基於 PCRE 模式。可點擊查看有關與 POSIX 正則表達式的不同和與 perl 的不同之處。
2.分隔符
當使用 PCRE 函數 的時候,模式需要由分隔符閉合包裹。分隔符可以使任意非字母數字、非反斜線、非空白字元。經常使用的分隔符是正斜線 /
、hash符號 #
以及取反符號 ~
。下麵的例子都是使用合法分隔符的模式。
/foo bar/ #^[^0-9]$# +php+ %[a-zA-Z0-9_-]%
如果分隔符需要在模式內進行匹配,它必須使用反斜線進行轉義。如果分隔符經常在模式內出現,一個更好的選擇就是是用其他分隔符來提高可讀性。例:
/http:\/\// #http://#
3.元字元
正則表達式的威力源於它可以在模式中擁有選擇和重覆的能力。一些字元被賦予特殊的含義,使其不再單純的代表自己,模式中的這種有特殊涵義的編碼字元 稱為元字元。
共有兩種不同的元字元:一種是可以在模式中方括弧外任何地方使用的,另外一種是需要在方括弧內使用的。
在方括弧外使用的元字元如下:
代碼 | 說明 |
---|---|
/ | 一般用於轉義字元 |
^ | 斷言目標的開始位置(或在多行模式下是行首) |
$ | 斷言目標的結束位置(或在多行模式下是行尾) |
. | 匹配除換行符外的任何字元(預設) |
[ | 開始字元類定義 |
] | 結束字元類定義 |
| | 開始一個可選分支 |
( | 子組的開始標記 |
) | 子組的結束標記 |
? | a:作為量詞,表示 0 次或 1 次匹配。b:位於量詞後面用於改變數詞的貪婪特性。 |
* | 量詞,0 次或多次匹配 |
+ | 量詞,1 次或多次匹配 |
{ | 自定義量詞開始標記 |
} | 自定義量詞結束標記 |
模式中方括弧內的部分稱為“字元類”。 在一個字元類中僅有以下可用元字元:
代碼 | 說明 |
---|---|
\ | 轉義字元 |
^ | 僅在作為第一個字元(方括弧內)時,表明字元類取反 |
- | 標記字元範圍 |
示例:
\ba\w*\b
匹配以字母 a 開頭的單詞,先是某個單詞開始處 \b ,然後是字母 a ,然後是任意數量的任意單詞字元(單詞字元指的是任意字母、數字、下劃線) \w* ,最後是單詞結束處 \b 。\d+
匹配1個或更多連續的數字。^\d{5,12}$
匹配為5位到12位數字,因為使用了 ^ 和 $ ,所以輸入的整個字元串都要用來和 \d{5,12} 來匹配,也就是說整個輸入必須是5到12個數字。
4.轉義序列(反斜線)
反斜線 \
有四種用法,詳細可點擊 轉義序列(反斜線)
【1】作為轉義字元,比如,如果你希望匹配一個 *
字元,就需要在模式中寫為 \*
。這適用於一個字元在不進行轉義會有特殊含義的情況下。 但是,對於非數字字母的字元,總是在需要其進行原文匹配的時候在它前面增加一個反斜線,來聲明它代表自己,這是安全的。如果要匹配一個反斜線,那麼在模式中使用 \\
。
反斜線在單引號字元串和雙引號字元串中都有特殊含義,因此要匹配一個反斜線, 模式中必須寫為 \\\\
。其中的原因:首先它作為字元串,反斜線會進行轉義。最後正則表達式引擎也認為反斜線是轉義。因此,需要 4 個反斜線才可以匹配一個反斜線。
【2】提供了一種對非列印字元進行可見編碼的控制手段
【3】用來描述特定的字元類
代碼 | 說明 |
---|---|
\d | 任意十進位數字 |
\D | 任意非十進位數字 |
\h | 任意水平空白字元(since PHP 5.2.4) |
\H | 任意非水平空白字元(since PHP 5.2.4) |
\s | 任意空白字元 |
\S | 任意非空白字元 |
\v | 任意垂直空白字元(since PHP 5.2.4) |
\V | 任意非垂直空白字元(since PHP 5.2.4) |
\w | 任意單詞字元,單詞字元指的是任意字母、數字、下劃線。 |
\W | 任意非單詞字元 |
【4】一些簡單的斷言。一個斷言指定一個必須在特定位置匹配的條件,它們不會從目標字元串中消耗任何字元。反斜線斷言包括:
\b
單詞邊界\B
非單詞邊界\A
目標的開始位置(獨立於多行模式)\Z
目標的結束位置或結束處的換行符(獨立於多行模式)\z
目標的結束位置(獨立於多行模式)\G
在目標中首次匹配位置
5.重覆/量詞
代碼 | 說明 |
---|---|
* | 重覆零次或更多次,等價於 |
+ | 重覆一次或更多次,等價於 |
? | 重覆零次或一次,等價於 |
{n} | 重覆n次 |
{n,} | 重覆n次或更多次 |
{n,m} | 重覆n到m次 |
預設情況下,量詞都是”貪婪”的,也就是說,它們會在不導致模式匹配失敗的前提下,儘可能多的匹配字元(直到最大允許的匹配次數)。然而,如果一個量詞緊跟著一個 ?
標記,它就會成為懶惰(非貪婪)模式, 它不再儘可能多的匹配,而是儘可能少的匹配。
下麵直接看示例,理解“貪婪”和“非貪婪”模式是怎麼回事。
對於字元串 "aa<div>test1</div>bb<div>test2</div>cc" 正則表達式 "<div>.*</div>" 匹配結果 "<div>test1</div>bb<div>test2</div>" 正則表達式 "<div>.*?</div>" 匹配結果 "<div>test1</div>"
關於更多“貪婪”和“非貪婪”模式的介紹可查閱 http://php.net/manual/zh/regexp.reference.repetition.php
6.字元類(方括弧)
PHP手冊中的描述:
-
左方括弧開始一個字元類的描述,並以方中括弧結束。單獨的一個右方括弧沒有特殊含義。如果一個右方括弧需要作為一個字元類中的成員,那麼可以將它寫在字元類的首字元處(如果使用了 ^ 取反,那麼是第二個)或者使用轉義符。
-
一個字元類在目標字元串中匹配一個單獨的字元;該字元必須是字元類中定義的字元集合的其中一個, 除非使用了 ^ 對字元類取反。如果^需要作為一個字元類的成員,確保它不是該字元類的首字元,或者對其進行轉義即可。
示例:
[aeiou] //匹配所有的小寫母音字母 [^aeiou] //匹配所有非母音字母的字元 [.?!] //匹配標點符號(.或?或!)
註意:^
只是一個通過枚舉指定那些不存在字元類之中的字元的便利符號。而不是斷言, 它仍然會從目標字元串中消耗一個字元,並且如果當前匹配點在目標字元串末尾, 匹配將會失敗。
輕鬆地指定一個字元範圍,範圍操作以 ASCII 整理排序。它們可以用於為字元指定數值,比如 [\000-\037]
[0-9] //代表的含意與 '\d' 就是完全一致的 [a-z0-9A-Z_] //完全等同於 '\w' 如果只考慮英文的話
下麵是一個更複雜的表達式 \(?0\d{2}[) -]?\d{8}
這個表達式可以匹配幾種格式的電話號碼,像 (010)88886666,或 022-22334455 ,或 02912345678 等。
簡單分析:首先是一個轉義字元 \(
,它能出現 0 次或 1 次 ?
,然後是一個數字 0 ,後面跟著 2 個數字 \d{2}
,然後是 )
或 -
或 “空格” 中的一個,它出現 0 次或 1 次,最後是 8 個數字 \d{8}
。
7.分支 ( | )
豎線字元用於分離模式中的可選路徑。比如模式 gilbert|Sullivan
匹配 ”gilbert” 或者 ”sullivan”。豎線可以在模式中出現任意多個,並且允許有空的可選路徑(匹配空字元串)。匹配的處理從左到右嘗試每一個可選路徑,並且使用第一個成功匹配的。如果可選路徑在子組(下麵定義)中,則”成功匹配”表示同時匹配了子模式中的分支以及主模式中的其他部分。
回看上文里的一個例子 \(?0\d{2}[) -]?\d{8}
這個正則也能匹配 010)12345678 或 (022-87654321 這樣的 “不正確” 的格式。其實我們可以利用分支就能解決這個問題,如下:
\({1}0\d{2}\){1}[- ]?\d{8}|0\d{2}[- ]?\d{8}
這個表達式匹配 3 位區號的電話號碼,其中區號可以用小括弧括起來,也可以不用,區號與本地號間可以用連字型大小或空格間隔,也可以沒有間隔。
使用分枝條件時,要註意各個條件的順序
8.內部選項設置
正則表達式在不同的模式修飾符下匹配出的結果有可能不相同。它的語法是 :(?修飾符)
比如,(?im)
設置表明多行大小寫不敏感匹配。同樣可以用它來取消這些設置,比如 (?im-sx)
設置了 “PCRE_CASELESS”,”PCRE_MULTILINE”,但是同時取消了 “PCRE_DOTALL” 和 “PCRE_EXTENDED”。如果一個字母即出現在 -
之前, 也出現在 -
之後,這個選項被取消設置。
下麵緊例舉簡單的示例,想要瞭解更多可點擊 內部選項設置 和 模式修飾符
示例:/ab(?i)c/
僅僅匹配 ”abc” 和 ”abC”
9.子組(子模式)
子組通過圓括弧分隔界定,並且它們可以嵌套。
示例:
字元串:"the red king" 正則表達式:((red|white) (king|queen)) 匹配結果:array("red king", "red king", "red", "king") 描述:其中第 0 個元素是整個模式匹配的結果,後面的三個元素依次為三個子組匹配的結果。 它們的下標分別為 1, 2, 3。
經常我們會有一種需求需要使用子組進行分組,但又不需要(單獨的)捕獲它們。在子組定義的左括弧後面緊跟字元串 ?:
會使得該子組不被單獨捕獲,並且不會對其後子組序號的計算產生影響。例如:
字元串:"the red king" 正則表達式:((?:red|white) (king|queen)) 匹配結果:array("red king", "red king", "king")
為了方便簡寫,如果需要在非捕獲子組開始位置設置選項, 選項字母可以位於 ?
和 :
之間,比如:
(?i:saturday|sunday) (?:(?i)saturday|sunday)
上面兩種寫法實際上是相同的模式。因為可選分支會從左到右嘗試每個分支,並且選項沒有在子模式結束前被重置,並且由於選項的設置會穿透對後面的其他分支產生影響,因此, 上面的模式都會匹配 ”SUNDAY” 以及 ”Saturday”。
再看一個匹配 IP 地址的正則 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
相關文章 IP地址的正則表達式
結語
上文中涉及 PHP 正則表達式中常用的語法,有的語法沒細說和涉及到的,如:模式修飾符、後向引用、斷言、遞歸模式,等。你可以通過 PHP 手冊查看這些內容。
提示:一般而言,對於同樣的功能,正則表達式函數運行效率要低於字元串函數。如果應用程式較簡單,那麼就用字元串表達式。但是,對於可以通過單個正則表達式執行的任務來說,如果使用多個字元串函數,則是不對的。 ---- 摘自《PHP 和 MySQL Web 開放》一書。
參考資料
http://php.net/manual/zh/book.pcre.php
https://msdn.microsoft.com/zh-cn/library/d9eze55x%28v=vs.80%29.aspx
http://deerchao.net/tutorials/regex/regex.htm
http://tool.chinaz.com/regex/
http://www.regexlab.com/zh/regref.htm