[TOC]
## 1 正則表達式定義
* 正則表達式(regular expression)描述了一種字符串匹配的模式,可以用來檢查一個串是否含有某種子串、將匹配的子串做替換或者從某個串中取出符合某個條件的子串等。
* 列目錄時, dir \*.txt或ls \*.txt中的\*.txt就不是一個正則表達式,因為這里\*與正則式的\*的含義是不同的。
* 正則表達式是由`普通字符`(例如字符 a 到 z)以及`特殊字符`(稱為元字符)組成的文字模式。正則表達式作為一個模板,將某個字符模式與所搜索的字符串進行匹配。
### 1.1 普通字符
由所有那些未顯式指定為元字符的打印和非打印字符組成。這包括所有的大寫和小寫字母字符,所有數字,所有標點符號以及一些符號。
### 1.2 非打印字符
| 字符 | 含義 |
| --- | --- |
| `\cx` | 匹配由x指明的控制字符。例如, \cM 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將c 視為一個原義的 ‘c’ 字符。|
| `\f` | 匹配一個換頁符。等價于 \x0c 和 \cL。|
| `\n` | 匹配一個換行符。等價于 \x0a 和 \cJ。|
| `\r` | 匹配一個回車符。等價于 \x0d 和 \cM。|
| `\s` | 匹配任何空白字符,包括空格、制表符、換頁符等等。等價于 [ \f\n\r\t\v]|
| `\S` | 匹配任何非空白字符。等價于 [^ \f\n\r\t\v]。|
| `\t` | 匹配一個制表符。等價于 \x09 和 \cI。|
| `\v` | 匹配一個垂直制表符。等價于 \x0b 和 \cK。|
### 1.3 特殊字符
所謂特殊字符,就是一些有特殊含義的字符,如上面說的`*.txt`中的`*`,簡單的說就是表示任何字符串的意思。如果要查找文件名中有*的文件,則需要對`*`進行轉義,即在其前加一個`\`,如` \*.txt`。正則表達式有以下特殊字符。
| 特別字符 | 說明 |
| :-- | :-- |
| `$` | 匹配輸入字符串的**結尾位置**。如果設置了 RegExp 對象的 Multiline 屬性,則 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,請使用 `\$`。 |
| `( )` | 標記一個子表達式的**開始和結束位置**。子表達式可以獲取供以后使用。要匹配`(`和`)`這些字符,請使用 `\(` 和 `\)`。【限定多選結構的范圍,標注量詞作用的元素,為反向引用“捕獲”文本】 |
|`*` | 匹配前面的子表達式**零次**或**多次**。要匹配 `*` 字符,請使用 `\*`。【匹配任意多次,也可能不匹配】 |
| `+` | 匹配前面的子表達式**一次**或**多次**。要匹配 `+ `字符,請使用 `\+`。【至少需要匹配一次,至多可能任意多次】 |
| `.` | 匹配**除換行符\n** 之外的**任何單字符**。要匹配 `.`,請使用 `\.`。【單個任意字符】 |
| `[` | 標記**一個中括號表達式的開始**。要匹配 `[`,請使用 `\[`。 |
| `?` | 匹配前面的子表達式**零次**或**一次**,或指明**一個非貪婪限定符**。要匹配 `?` 字符,請使用 `\?`。【容許匹配一次,但非必須】 |
| `\` | 將下一個字符標記為**特殊字符**、或**原義字符**、或**向后引用**、或**八進制轉義符**。例如, `n` 匹配字符 `n`。`\n` 匹配`換行符`。序列 `\\` 匹配 `\`,而 `\(` 則匹配 `(`。 |
| `^` | 匹配輸入字符串的**開始位置**,除非在方括號表達式中使用,此時它表示**不接受該字符集合**。要匹配 `^` 字符本身,請使用 `\^`。 |
| `{` | 標記限定符表達式的`開始`。要匹配 `{`,請使用 `\{`。 |
| `|` | 指明**兩項之間的一個選擇(或)**。要匹配 `|`,請使用 `\|`。 |
| `[...]` | **字符組**。【匹配單個列出的字符】 |
| `[^...]` | **排除型**字符組。【匹配單個未列出的字符】 |
| `{min,max}` | **區間**量詞,**至少**需要min次,**至多**容許max次 |
| `\<`| 單詞分界符。匹配單詞的**開始位置** |
| `\>` | 單詞分界符。匹配單詞的**結束位置**|
* 使用括號的3個理由是: **限制多選結構**、**分組**和**捕獲文本**
* `-i`的參數很有用, 它能進行**忽略大小寫**的匹配
* 構造正則表達式的方法和創建數學表達式的方法一樣。也就是用多種元字符與操作符將小的表達式結合在一起來創建更大的表達式。正則表達式的組件可以是單個的字符、字符集合、字符范圍、字符間的選擇或者所有這些組件的任意組合。
### 1.4 限定符
* 定符用來指定正則表達式的一個給定組件必須要出現多少次才能滿足匹配。有*或+或?或{n}或{n,}或{n,m}共6種。
* `*`、`+`和`?`限定符都是貪婪的,因為它們會盡可能多的匹配文字,只有在它們的**后面加上一個?** 就可以實現非貪婪或最小匹配。
正則表達式的限定符有:
| 字符 | 描述 |
| :-- | :-- |
| `*` | 匹配前面的子表達式**零次**或**多次**。例如,`zo*` 能匹配 “z” 以及 “zoo”。`*` 等價于`{0,}`。 |
| `+` | 匹配前面的子表達式**一次**或**多次**。例如,`zo+` 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。`+` 等價于 `{1,}`。 |
| `?` | 匹配前面的子表達式**零次**或**一次**。例如,`do(es)?` 可以匹配 “do” 或 “does” 中的”do” 。`?` 等價于 `{0,1}`。 |
| `{n}` | n 是一個非負整數。匹配確定的 **n 次**。例如,`o{2}` 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的兩個 o。 |
| `{n,}` | n 是一個非負整數。**至少匹配n 次**。例如,`o{2,}` 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。`o{1,}` 等價于 `o+`。`o{0,}` 則等價于 `o*`。 |
| `{n,m}` | m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,`o{1,3}` 將匹配 “fooooood” 中的前三個 o。`o{0,1}` 等價于 `o?`。請注意在逗號和兩個數之間不能有空格。 |
### 1.5 定位符
用來**描述字符串或單詞的邊界**,`^`和`$`分別指字符串的**開始**與**結束**,`\b`描述單詞的前或后邊界,`\B`表示非單詞邊界。不能對定位符使用限定符。
### 1.6 選擇
用**圓括號將所有選擇項括起來**,相鄰的**選擇項之間用|分隔**。但用圓括號會有一個副作用,是相關的匹配會被緩存,此時可用`?:`放在第一個選項前來消除這種副作用。
其中`?:`是非捕獲元之一,還有兩個非捕獲元是`?=`和`?!`,這兩個還有更多的含義,前者為**正向預查**,在任何開始匹配圓括號內的正則表達式模式的位置來匹配搜索字符串,后者為**負向預查**,在任何開始不匹配該正則表達式模式的位置來匹配搜索字符串。
### 1.7 后向引用
對一個正則表達式模式或部分模式兩邊添加圓括號將導致相關匹配存儲到一個臨時緩沖區中,所捕獲的每個子匹配都按照在正則表達式模式中從左至右所遇到的內容 存儲。存儲子匹配的緩沖區編號從 1 開始,連續編號直至最大 99 個子表達式。每個緩沖區都可以使用 ‘\n’ 訪問,其中 n 為一個標識特定緩沖區的一位或兩位十進制數。
> 可以使用非捕獲元字符 `?:`, `?=`, `?!` 來忽略對相關匹配的保存。
## 2. 各種操作符的運算優先級
相同優先級的**從左到右**進行運算,不同優先級的運算**先高后低**。各種操作符的優先級從高到低如下:
| 操作符 | 描述 |
| :-- | :-- |
| `\` | 轉義符 |
| `()`, `(?:)`, `(?=)`, `[]` | 圓括號和方括號 |
| `*`, `+`, `?`, `{n}`, `{n,}`, `{n,m}` | 限定符 |
| `^`, `$`, `\anymetacharacter(任何元字符)` | 位置和順序 |
| `|` | “或”操作 |
## 3. 全部符號解釋
| 字符 | 描述 |
| :-- | :-- |
| `\` | 將下一個字符標記為**特殊字符**、或**原義字符**、或**向后引用**、或**八進制轉義符**。例如, `n` 匹配字符 `n`。`\n` 匹配`換行符`。序列 `\\` 匹配 `\`,而 `\(` 則匹配 `(`。 |
| `^` | 匹配輸入字符串的**開始位置**。如果設置了 RegExp 對象的 Multiline 屬性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。 |
| `$` | 匹配輸入字符串的**結束位置**。如果設置了RegExp 對象的 Multiline 屬性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。 |
| `*` | 匹配前面的子表達式**零次**或**多次**。例如,`zo*` 能匹配 “z” 以及 “zoo”。`\*` 等價于`{0,}`。 |
| `+` | 匹配前面的子表達式**一次**或**多次**。例如,`zo+` 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。`+` 等價于 `{1,}`。 |
| `?` | 匹配前面的子表達式**零次**或**一次**。例如,`do(es)?` 可以匹配 “do” 或 “does” 中的”do” 。`?` 等價于 `{0,1}`。 |
| `{n}` | n 是一個非負整數。匹配確定的 n 次。例如,`o{2}` 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的兩個 o。 |
| `{n,}` | n 是一個非負整數。至少匹配n 次。例如,`o{2,}` 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。`o{1,}` 等價于 `o+`。`o{0,}` 則等價于 `o*`。 |
| `{n,m}` | m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,`o{1,3}` 將匹配 “fooooood” 中的前三個 o。`o{0,1}` 等價于 `o?`。請注意在逗號和兩個數之間不能有空格。 |
| `?` | 當該字符緊跟在任何一個其他限制符 (`*`, `+`, `?`, `{n}`, `{n,}`, `{n,m}`) 后面時,匹配模式是**非貪婪**的。非貪婪模式**盡可能少**的匹配所搜索的字符串,而默認的貪婪模式則**盡可能多**的匹配所搜索的字符串。例如,對于字符串 “oooo”,`o+?` 將匹配單個 “o”,而 `o+` 將匹配所有 ‘o’。 |
| `.` | 匹配除 換行符“\n” 之外的任何單個字符。要匹配包括 ‘\n’ 在內的任何字符,請使用象 `[.\n]` 的模式。 |
| `(pattern)` | 匹配 pattern 并獲取這一匹配。所獲取的匹配可以從產生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中則使用 $0…$9 屬性。要匹配圓括號字符,請使用 `\(` 或 `\)`。 |
| `(?:pattern)` | 匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以后使用。這在使用 “或” 字符 `(|)` 來組合一個模式的各個部分是很有用。例如, `industr(?:y|ies)` 就是一個比 ‘industry|industries’ 更簡略的表達式。 |
| `(?=pattern)` | 正向預查,在任何匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,`Windows (?=95|98|NT|2000)` 能匹配 “Windows 2000″ 中的 “Windows” ,但不能匹配 “Windows 3.1″ 中的 “Windows”。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。 |
| `(?!pattern)` | 負向預查,在任何不匹配 pattern 的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如`Windows (?!95|98|NT|2000)` 能匹配 “Windows 3.1″ 中的 “Windows”,但不能匹配 “Windows 2000″ 中的 “Windows”。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始 |
| `x|y` | 匹配 x 或 y。例如,`z|food` 能匹配 “z” 或 “food”。`(z|f)ood` 則匹配 “zood” 或 “food”。 |
| `[xyz]` | 字符集合。匹配所**包含**的任意一個字符。例如, `[abc]` 可以匹配 “plain” 中的 ‘a’。 |
| `[^xyz]` | 負值字符集合。匹配**未包含**的任意字符。例如, `[^abc]` 可以匹配 “plain” 中的’p'。 |
| `[a-z]` | 字符范圍。匹配**指定范圍內**的任意字符。例如,`[a-z]` 可以匹配 ‘a’ 到 ‘z’ 范圍內的任意小寫字母字符。 |
| `[^a-z]` | 負值字符范圍。匹配任何不在指定范圍內的任意字符。例如,`[^a-z]` 可以匹配任何不在 ‘a’ 到 ‘z’ 范圍內的任意字符。 |
| `\b` | 匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, `er\b` 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 |
| `\B` | 匹配非單詞邊界。`er\B` 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 |
| `\cx` | 匹配由 x 指明的控制字符。例如, `\cM` 匹配一個 Control-M 或回車符。x 的值必須為 A-Z 或 a-z 之一。否則,將 c 視為一個原義的 ‘c’ 字符。 |
| `\d` | 匹配一個**數字**字符。等價于 `[0-9]`。 |
| `\D` | 匹配一個**非數字**字符。等價于 `[^0-9]`。 |
| `\f` | 匹配一個**換頁符**。等價于 `\x0c` 和 `\cL`。 |
| `\n` | 匹配一個**換行符**。等價于 `\x0a` 和 `\cJ`。 |
| `\r` | 匹配一個**回車符**。等價于 `\x0d` 和 `\cM`。 |
| `\s` | 匹配任何**空白字符**,包括**空格**、**制表符**、**換頁符**等等。等價于 `[ \f\n\r\t\v]`。 |
| `\S` | 匹配任何**非空白字符**。等價于 `[^\f\n\r\t\v]`。 |
| `\t` | 匹配一個**制表符**。等價于 `\x09` 和 `\cI`。 |
| `\v` | 匹配一個**垂直制表符**。等價于 `\x0b` 和 `\cK`。 |
| `\w` | 匹配**包括下劃線的任何單詞字符**。等價于`[A-Za-z0-9_]`。【所有單個大小寫字母、數字、下劃線】 |
| `\W` | 匹配任何**非單詞字符**。等價于 `[^A-Za-z0-9_]`。【所有單個非大小寫字母、非數字、非下劃線】 |
| `\xn` | 匹配 n,其中 n 為十六進制轉義值。十六進制轉義值必須為確定的兩個數字長。例如,`\x41` 匹配 “A”。`\x041` 則等價于 `\x04` & `1`。**正則表達式中可以使用 ASCII 編碼**。 |
| `\num` | 匹配 num,其中** num 是一個正整數**。對所獲取的匹配的引用。例如,`(.)\1` 匹配兩個連續的相同字符。 |
| `\n` | 標識一個八進制轉義值或一個向后引用。如果 `\n` 之前至少 n 個獲取的子表達式,則 n 為向后引用。否則,如果 n 為八進制數字 (0-7),則 n 為一個八進制轉義值。 |
| `\nm` | 標 識一個八進制轉義值或一個向后引用。如果 `\nm` 之前至少有 nm 個獲得子表達式,則 nm 為向后引用。如果 \\nm 之前至少有 n 個獲取,則 n 為一個后跟文字 m 的向后引用。如果前面的條件都不滿足,若 n 和 m 均為八進制數字 (0-7),則 `\nm` 將匹配八進制轉義值 nm。 |
| `\nml` | 如果 n 為八進制數字 (0-3),且 m 和 l 均為八進制數字 (0-7),則匹配八進制轉義值 nml。 |
| `\un` | 匹配 n,其中 n 是一個用四個十六進制數字表示的 Unicode 字符。例如, `\u00A9` 匹配版權符號 (?)。 |
## 4. 部分例子
| 正則表達式 | 說明 |
| :-- | :-- |
| `/\b([a-z]+) \1\b/gi` | 一個單詞連續出現的位置 |
| `/(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/` | 將一個URL解析為協議、域、端口及相對路徑 |
| `/^(?:Chapter|Section) [1-9][0-9]{0,1}$/`| 定位章節的位置 |
| `/[-a-z]/` | A至z共26個字母再加一個-號。 |
| `/ter\b/` | 可匹配chapter,而不能terminal |
| `/\Bapt/` | 可匹配chapter,而不能aptitude |
| `/Windows(?=95 |98 |NT )/` | 可匹配Windows95或Windows98或WindowsNT,當找到一個匹配后,從Windows后面開始進行下一次 |
## 5. 正則表達式匹配規則
### 5.1 基本模式匹配
一切從最基本的開始。模式,是正規表達式最基本的元素,它們是一組描述字符串特征的字符。模式可以很簡單,由普通的字符串組成,也可以非常復雜,往往用特殊的字符表示一個范圍內的字符、重復出現,或表示上下文。
例如:`^once`
這個模式包含一個特殊的字符`^`,表示該模式只匹配那些**以once開頭**的字符串。例如該模式與字符串”once upon a time”匹配,與”There once was a man from NewYork”不匹配。正如如`^`符號表示開頭一樣,`$`符號用來匹配那些以給定模式結尾的字符串。
例如:`bucket$`
這個模式與”Who kept all of this cash in a bucket”匹配,與”buckets”不匹配。字符`^`和`$`同時使用時,表示**精確匹配**(字符串與模式一樣)。
例如:`^bucket$`
**只匹配字符串”bucket”**。如果一個模式不包括^和$,那么它與任何包含該模式的字符串匹配。例如:模式
例如:`once`與字符串 `There once was a man from NewYork Who kept all of his cash in a bucket.`**是匹配的**。
在該模式中的字母(o-n-c-e)是字面的字符,也就是說,他們表示該字母本身,數字也是一樣的。其他一些稍微復雜的字符,如**標點符號**和**空白字符**(空格、 制表符等),要用到**轉義序列**。所有的轉義序列都用**反斜杠**(`\`)打頭。制表符的轉義序列是:`\t`。所以如果我們要檢測一個字符串是否以制表符開頭,可以用這 個模式:
例如: `^\t`
類似的,用`\n`表示“換行”,`\r`表示回車。其他的特殊符號,可以用在前面加上反斜杠,如反斜杠本身用`\\`表示`\`,句號`.`用`\.`表示,以此類推。
### 5.2 字符簇
在INTERNET的程序中,**正規表達式通常用來驗證用戶的輸入**。當用戶提交一個FORM以后,要判斷輸入的**電話號碼**、**地址**、**EMAIL地址**、**信用卡號碼**等是否有效,用普通的基于字面的字符是不夠的。
所以要用一種更自由的描述我們要的模式的辦法,它就是**字符簇**。要建立一個表示所有元音字符的字符簇,就把所有的元音字符放在一個方括號里:
例如:`[AaEeIiOoUu]`
這個模式與任何元音字符匹配,但只能表示一個字符。用連字號可以表示一個字符的范圍,如:
`[a-z]` //匹配所有的小寫字母
`[A-Z]` //匹配所有的大寫字母
`[a-zA-Z]` //匹配所有的字母
`[0-9]` //匹配所有的數字
`[0-9\.\-]` //匹配所有的數字,句號和減號
`[\f\r\t\n]` //匹配所有的空白字符
同樣的,這些也只表示一個字符,這是一個非常重要的。如果要匹配一個由一個小寫字母和一位數字組成的字符串,比如”z2″、”t6″或”g7″,但不是”ab2″、”r2d3″ 或”b52″的話,用這個模式:
例如: `^[a-z][0-9]$`
盡管`[a-z]`代表26個字母的范圍,但在這里它只能與第一個字符是小寫字母的字符串匹配。
前面曾經提到`^`表示字符串的開頭,但它還有另外一個含義。當在一組**方括號里使用**`^`是,它表示“**非**”或“**排除**”的意思,常常用來剔除某個字符。還用前面的例子,我們要求第一個字符不能是數字:
例如:`^[^0-9][0-9]$`
這個模式與”&5″、”g7″及”-2″是匹配的,但與”12″、”66″是不匹配的。下面是幾個排除特定字符的例子:
`[^a-z]` //除了小寫字母以外的所有字符
`[^\\\/\^] ` //除了(`\`)(`/`)(`^`)之外的所有字符
`[^\"\']` //除了雙引號(`"`)和單引號(`'`)之外的所有字符
特殊字符`.` (點,句號)在正規表達式中用來表示匹配除了“換行”之外的所有字符。所以模式`^.5$`與任何兩個字符的、以數字5結尾和以其他非“換行”字符開頭的字符串匹配。模式`.`可以匹配任何字符串,除了空串和只包括一個“換行”的字符串。
PHP的正規表達式有一些內置的通用字符簇,列表如下:
字符簇含義
`[[:alpha:]]` 任何字母
`[[:digit:]]` 任何數字
`[[:alnum:]]` 任何字母和數字
`[[:space:]]` 任何白字符
`[[:upper:]]` 任何大寫字母
`[[:lower:]]` 任何小寫字母
`[[:punct:]]` 任何標點符號
`[[:xdigit:]]` 任何16進制的數字,相當于`[0-9a-fA-F]`
### 5.3 確定重復出現
到現在為止,你已經知道如何去匹配一個字母或數字,但更多的情況下,可能要匹配一個單詞或一組數字。一個單詞有若干個字母組成,一組數字有若干個單數組成。跟在字符或字符簇后面的花括號(`{}`)用來確定前面的內容的重復出現的次數。
字符簇含義
`^[a-zA-Z_]$` 所有的字母和下劃線
`^[[:alpha:]]{3}$` 所有的3個字母的單詞
`^a$` 字母a
`^a{4}$` aaaa
`^a{2,4}$` aa,aaa或aaaa
`^a{1,3}$` a,aa或aaa
`^a{2,}$` 包含多于兩個a的字符串
`^a{2,}` 如:aardvark和aaab,但apple不行
`a{2,}` 如:baad和aaa,但Nantucket不行
`\t{2}` 兩個制表符
`.{2}` 所有的兩個字符
這些例子描述了花括號的三種不同的用法。一個數字,`{x}`的意思是**前面的字符或字符簇只出現x次**;一個數字加逗號,`{x,}`的意思是**前面的內容出現 x或更多的次數**;兩個用逗號分隔的數字,`{x,y}`表示**前面的內容至少出現x次,但不超過y次**。我們可以把模式擴展到更多的單詞或數字:
`^[a-zA-Z0-9_]{1,}$` //所有包含一個以上的字母、數字或下劃線的字符串
`^[0-9]{1,}$` //所有的正數
`^\-{0,1}[0-9]{1,}$` //所有的整數
`^\-{0,1}[0-9]{0,}\.{0,1}[0-9]{0,}$` //所有的小數
最后一個例子不太好理解,是嗎?這么看吧:與所有以一個可選的負號(`\-{0,1}`)開頭(`^`)、跟著0個或更多的數字(`[0-9]{0,}`)、和一個可選的小數點(`\.{0,1}`)再跟上0個或多個數字(`[0-9]{0,}`),并且沒有其他任何東西(`$`)。下面你將知道能夠使用的更為簡單的方法。
特殊字符`?`與`{0,1}`是相等的,它們都代表著:“**0個或1個前面的內容**”或“前面的內容是可選的”。所以剛才的例子可以簡化為:
`^\-?[0-9]{0,}\.?[0-9]{0,}$
`
特殊字符`*`與`{0,}`是相等的,它們都代表著“**0個或多個前面的內容**”。最后,字符`+`與 `{1,}`是相等的,表示“**1個或多個前面的內容**”,所以上面的4個例子可以寫成:
`^[a-zA-Z0-9_]+$` //所有包含一個以上的字母、數字或下劃線的字符串
`^[0-9]+$` //所有的正數
`^\-?[0-9]+$` //所有的整數
`^\-?[0-9]*\.?[0-9]*$` //所有的小數
匹配美元金額 `@\$[0-9]+(?:\.[0-9]+)?@i
`
在字符組內部, 元字符的定義規則(及它們的意義) 是不一樣的。例如, 在字符組外部, 點號是元字符, 但是在內部則不是如此。相反, 連字符只有在字符組內部(這是普遍情況) 才是元字符, 否則就不是。脫字符在字符組外部表示一個意思, 在字符組內部緊接著[時表示另一個意思, 其他情況下又表示別的意思。
●**不要混淆多選項和字符組**。字符組`[abc]` 和多選項`(a|b|c)` 固然表示同一個意思, 但是這個例子中的相似性并不能推廣開來。無論列出的字符有多少, **字符組**只能匹配一個字符。相反, **多選項**可以匹配任意長度的文本, 每
個多選項可能匹配的文本都是獨立的, 例如`\<(1,000,000|million|thousand·thou)\>` 。不過, 多選項沒有像字符組那樣的排除功能。
●**排除型字符組是表示所有未列出字符的字符組的簡便方法**。因此, `[^x]` 的意思并不是“只有當這個位置不是x時才能匹配”, 而是說“匹配一個不等于x的字符”。其中的差別很細微, 但很重要。例如, 前面的概念可以匹配一個空行, 而`[^x]` 則不行。
●`-i`**參數規定在匹配時不區分大小寫**
`@([A-Za-z]+).+\1@is
`
在支持反向引用的工具軟件中, 括號能夠**記憶**其中的子表達式匹配的文本。當然, 在一個表達式中我們可以使用多個括號。再用`\1` ,`\2` ,`\3` 等來表示第一、第二、第三組括號匹配的文本
變量名:`[a-zA-Z_][a-zA-Z_0-9]* `如果長度有限制 用區間量詞 `{0,31}` 替代最后的`*`
匹配引號內的字符串 `"[^"]*"`
我們用`[^"]` 來匹配除雙引號之外的任何字符, 用`*` 來表示兩個引號之間可以存在任意數目的非雙引號字符
**NOTE**: 不管 Ignore Case 是否設置為 True,在這種情況下,`\d`與`\D`總是區分大小寫的,下面將介紹的也是一樣
## 6. 貪婪匹配和惰性匹配
### 6.1 定義
**貪婪匹配(greedy)**: 它會**匹配盡可能多的字符**。它首先看整個字符串,如果不匹配,對字符串進行收縮;遇到可能匹配的文本,停止收縮,對文本進行擴展,當發現匹配的文本時,它不著急將該匹配保存到匹配集合中,而是對文本繼續擴展,直到無法繼續匹配 或者 擴展完整個字符串,然后將前面最后一個符合匹配的文本(也是最長的)保存起來到匹配集合中。所以說它是貪婪的。
**惰性匹配(lazy)**:它會**匹配盡可能少的字符**,它從第一個字符開始找起,一旦符合條件,立刻保存到匹配集合中,然后繼續進行查找。所以說它是懶惰的。
| 貪婪匹配 | 惰性匹配 | 匹配描述 |
| :-- | :-- | :-- |
| `?` | `??` | 匹配0個或1個 |
| `+` | `+?` | 匹配1個或多個 |
| `\*` | `\*?` | 匹配0個或多個 |
| `{n}` | `{n}?` | 匹配n個 |
| `{n,m}` | `{n,m}?` | 匹配n個或m個 |
| `{n,}` | `{n,}?` | 匹配n個或多個 |
### 6.2 貪婪匹配的匹配過程

### 6.3 惰性匹配的匹配過程

## 7. 匹配邊界
### 7.1定義
正則表達式中,可以在**字符前**加`\b`,來匹配其 **后面** 的字符位于字符串首位的字符 。
正則表達式中,可以在 **字符后** 加`\b`,來匹配其 **前面** 的字符位于字符串末位的字符
通常情況下,以 **空格**、**段落首行**、**段落末尾**、**逗號**、**句號** 等符號作為邊界,值得注意的是,分隔`-`也可以作為邊界
**NOTE**: 這里有個重要的搜索引擎優化常識,大家注意到本文檔的命名,我采用的是:Regular-Expression-Tutorial.pdf,為什么不用下劃線分隔,命名成Regular_Expression_Tutorial.pdf 呢? 因為當搜索引擎看到`-`的時候,會把它視為一個**空格**(' '),而看到下劃線`_`的時候,會把它視為**空字符**(''),實際上,下劃線的正確叫法是**連字符**。于是,當我命名為 Regular-Expression-Tutorial.pdf 時,搜索引擎看到的是:Regular Expression Tutorial.pdf,而當我命名成 Regular_Expression_Tutorial.pdf 時,搜索引擎看作 RegularExpressionTutorial.pdf 。
可以看出,正則表達式在字符邊界問題上 對`-`的處理方式 與 搜索引擎相同
### 7.2 邊界的相對性
當你對一個普通字符,比如“s”,設定邊界的時候,它的邊界是諸如**空格**、**分隔符**、**逗號**、**句號**等。
當你對一個邊界,比如分隔符`-`或者`,`等,設定邊界的時候,它的邊界是**普通字符**
### 7.3 匹配文本首
在正則表達式中,可以在 匹配模式 的第一個字符前添加 `^`,以匹配滿足模式且位于全部文本之首的字符串。 可以將它的匹配方式理解成這樣:1、假設不存在`^`,進行一個正常匹配,將所有匹配的文本保存到匹配集合中;2、在匹配集合中尋找位于 所搜索的文本 首位的匹配;3、從匹配集合中刪除其他匹配,僅保留該匹配
### 7.4 匹配文本末
在正則表達式中,可以在 匹配模式 的最后一個字符后添加 `$`,以匹配 滿足模式且位于全部文本之末的字符串
回顧下之前介紹的,可以看出: `\b`和`\B`是對 匹配模式(表達式) 中**某個字符**出現的進行位置(單詞首位還是末位)進行限制。`^`和`$` 是對 **整個待搜索文本** 的 匹配模式(表達式) 出現位置(文本首位還是文本末位)進行限制。它們的關系是**一小一大**
## 8. 匹配子模式
在正則表達式中,可以使用`(`和`)`將模式中的子字符串括起來,以形成一個**子模式**。將子模式視為一個整體時,那么它就相當于一個單個字符
例:
```php
$str = 'This is the first line.<br>
This is the second line.<br><br/><br />
This is the third line.<br>>>>>';
preg_match('@(<br\s*/?>){2,}@is',$str,$matches);
```
匹配過程理解成這樣:子模式`(<br\s*/?>)`首先匹配所有`<br>`、`<br/>`或`<br />`;然后,將每一個匹配結果視為一個整體(相當于單個字符);接著,匹配這個整體連續出現兩次或以上的文本。
“或”匹配
在正則表達式中,可以使用`|`將一個表達式拆分成兩部分“reg1|reg2”,它的意思是:匹配所有符合表達式 reg1 的文本 **或者** 符合表達式 reg2 的文本
```php
$str = 'The <b>text of</b> this row is bold.
The <i>text of</i> this row is italic.';
preg_match_all('@</?b>|</?i>@is',$str,$matches);
```
在子模式中使用“或”匹配
從上面的定義應該可以看出,“|”分隔的是整個表達式,而有的時候,我們希望分隔的是一個表達式的一部分,比如說,我們想要匹配 “1900”到“2099”的所有年份。
```php
$str = '1932 is supposed to be matched as a whole, but it is matched only part of it.
2055 is mathced in the right way.
3019 is out of range, but it\'s still matched partly';
preg_match_all('@(19|20)\d{2}@is',$str,$matches);
```
嵌套子模式
匹配 1900 年1 月 1 日 到 2000 年 1 月 1 日 除過閏年外的所有正確日期
```php
$str = 'These dates are matched: 1900-1-1、 1928-2-28、 1931-11-30、 2000-1-1、 1999-10-30
These dates are not matched: 1900-1-32、 1928-2-29、 2000-01-1、 1982-12-08';
```
1. 首位可以是 19 也可以是 20; Reg: `19|20`
2. 當是 19 的時候,后面可以是 00 到 99 中任意數字; RegEx: `19\d{2}|20
`
3. 當是 20 的時候,只能匹配 00; Reg: `19\d{2}|2000`
4. 月份可以是 1 到 9,或者 10 到 12; Reg: `(19\d{2}|2000)-([1-9]|1[0-2])
`
因為天數與月份相關,所以將 `([1-9]|1[0-2])` 拆分為下面三個子模式:
5. 當月份是 2 的時候,天數是 28; Reg: `2-([1-9]\b|1\d|2[0-8])
`
6. (**1、3、5、7、 8、 10、 12**) 月, 天數是 31; Reg: `([13578]|1[02])-([1-9]\b|[12]\d|3[01])`
7. (**4、6、9、11**) 月,天數是 30; Reg: `([469]|11)-([1-9]\b|[12]\d|30)
`
**NOTE**: 注意上面日期部分的匹配,分成了兩部分,月和日;對于月來說,如果我們要匹配大月(31 天的月),寫法是:`[13578]|1[0-2]`;而日期部分,比如說要匹配 31 天,它又由三部分組成:`[1-9]`表示 1 號到 9 號;`[12]\d` 表示 10 號到 29 號;`3[01]`表示 30 號到 31 號。
**還有個地方需要注意**:單詞邊界問題,如果你這樣寫表達式: `2-([1-9]|1\d|2[0-8])`,對于 2-29 這樣不應該匹配的日期,會匹配它合法的部分 2-29,因為2-2滿足2-[1-9]。回顧下我之前講述的內容,我們還必須規定,當天數是個位數時,它必須處于單詞邊界`[1-9]\b`。
```
preg_match_all('@(19\d{2}|2000)-(2-([1-9]\b|1\d|2[0-8])|([13578]|1[02])-([1-9]\b|[12]\d|3[01])|([469]|11)-([1-9]\b|[12]\d|30))@is',$str,$matches);
```
Result:These dates are matched: `1900-1-1`、 `1928-2-28`、 `1931-11-30`、 `2000-1-1`、 `1999-10-30`
These dates are not matched: `1900-1-32`、 `1928-2-29`、 `2000-01-1`、 `1982-12-08`
## 9. 后向引用
1.匹配重復單詞
2.匹配有效的 HTML 標記
```php
$str = 'Is the cost of of gasline going up up?
Look up of the TV, your mobile phone is there.';
```
惰性匹配:`((of|up)\b ??){2}
`
正則表達式中,使用`\數字`來進行**后向引用**,數字表示這里引用的是前面的第幾個子模式。
```php
preg_match_all('@(of|up) \1@is',$str,$matches);
```
這一次,我們獲得了預期的效果,`(of|up) \1`的含義是: 如果前面 子模式 1 匹配了“of”,那么`\1`就代表“of”;如果 子模式 1 匹配了 “up”,那么`\1`就代表“up”,整個表達式相當于“of of|up up”
我們不知道哪些單詞重復,這時候,就只能使用后向引用來完成
```php
$str = 'Is the cost of of gasline going up up?
Look up of the TV, your mobile phone is there.
You are the best of the the best';
preg_match_all('@(\w+) \1@is',$str,$matches);
```
```php
$str = '<h1>This is a valid header</h1>
<h2>This is not valid.</h2>';
preg_match_all('@<h([1-6])>.*?</h\1>@is',$str,$matches);
```
## 10. 文本替換
### 10.1 使用后向引用進行文本替換
**正則表達式的三部曲**應該是:1、查找;2、引用匹配了的文本(后向引用);3、有選擇地替換文本
**需要注意的是**:大部分語言的正則表達式實現,**在查找中**,使用后向引用來代表一個子模式,其語法是`\數字`;而**在替換中**,其語法是`$數字`
```php
$str = '<h1>This is a valid header</h1>
<h2>This is not valid.</h3>';
$matches = preg_replace('@<h1>(.*?)</h1>@is','<h1 style="background:#ff0">$1</h1>',$str);
```
### 10.2 替換電話號碼格式
```
$str = '(020)82514769
(021)83281314
(029)88401132';
$matches = preg_replace('@\((\d{3})\)(\d{8})@is','$1-$2',$str);
```
```
Result
020-82514769
021-83281314
029-88401132
```
## 11. 預查和非獲取匹配
獲取的 Windows 的所有版本
```
$str = 'Windows 1.03 and Windows 2.0 fisrt Released in 1985 and 1987 respectively.
Windows 95 and Windows 98 are the successor.
Then Windows 2000 and Windows Xp appeared.
Windows Vista is the Latest version of the family.';
preg_match_all('@Windows [\w.]+\b@is',$str,$matches);
```
將所有的 Windows,全部換成簡寫 Win,并去掉 Windows 與 版本號 之間的空格,我們則需要使用后向引用
```
$matches = preg_replace('@Windows ([\w.]+\b)@is','Win$1',$str);
```
我們首先查看一下表達式的區別, 為了要使用后向引用,我們用`(`和`)`把`[\w.]+\b`包起來,使它成為一個**子模式**。 我們知道,只有這樣,才可以用 `$1` 去引用它,這里,我們發現使用子模式的一個作用: 系統會在幕后將所有的子模式保存起來,以供后向引用使用(包含查找時的后向引用 和 替換時的后向引用)。
正則表達式中,可以在子模式內部前面加`?:`來表示這個子模式是一個 **非獲取匹配**,非獲取匹配不會被保存,不能在后向引用中獲取
**2.正向預查**
```
preg_match_all('@Windows (?=[\d.]+\b)@is',$str,$matches);
```
它的語法是在 子模式內部 前面加`?=`,表示的意思是:首先,要匹配的文本必須滿足此子模式 前面 的表達式(本例,“Windows ”);其次,此子模式不參與匹配
這次,你大概了解了 “非獲取匹配” 這五個漢字的含義,它們僅僅起一個限制作用,不參與匹配。 你可以將 正向預查 理解成為自定義的邊界(`\b`),這個邊界位于 表達式末。
反言之,你可以將位于表達式末的 `\b` 理解成非獲取匹配的一個特例: `(?=[ ,.\r\n<>;\-])`。注意,這里我沒有寫全邊界符號
1. 先進行普通匹配:`Windows ([\d.]+\b)
`
2. 然后從匹配文本中將 子模式 內的文本排除掉。
**3.反向預查**
要求僅匹配金額,而不匹配前面的 “CNY:”
```
$str = 'CNY: 128.04
USD: 22.5
USD: 23.5
HKD: 1533.5
CNY: 23.78';
preg_match_all('@(?<=CNY: )\d+\.\d+@is',$str,$matches);
```
反向預查 的語法是在子模式內部前面加`?<=`,表示的意思是:首先,要匹配的文本必須滿足此子模式 后面 的表達式(本例,`\d+.\d+`);其次,此子模式不參與匹配。
你可以將 反向預查 理解成為自定義的邊界(`\b`),這個邊界位于 表達式首。
反言之,你可以將位于 表達式首 的 `\b` 理解成一個非獲取匹配的一個特例:`(?<=[ ,.\r\n<>;\-])`。 注意,我沒有寫全所有邊界
1. 先進行普通匹配:`(CNY: )\d+\.\d+
`
2. 然后從匹配文本中將 子模式 內的文本排除掉
**4.正向、反向預查組合**
```
$str = '<h1>This is header.</h2>
<h2>This is header,too.</h2>
<span>This is not a header.</span>';
preg_match_all('@(?<=<h(?<number>[1-6])>).*?(?=</h\k<number>>)@is',$str,$matches);
```
`\k<number>` 命名后向引用
**5.負正向預查、負反向預查**
在正則表達式中,可以在子模式內部前面加 `?!` 來形成一個 負正向預查,它的效果與`?=` 相反
```
$str = 'Windows 1.03 and Windows 2.0 fisrt Released in 1985 and 1987 respectively.
Windows 95 and Windows 98 are the successor.
Then Windows 2000 and Windows Xp appeared.
Windows Vista is the Latest version of the family.';
preg_match_all('@Windows(?! [\d.]+\b)@is',$str,$matches);
```
在正則表達式中,可以在子模式內部前面加 `?<!` 來形成一個 負反向預查,它的效果與`?<=` 相反。
```
$str = 'CNY: 128.04
USD: 22.5
USD: 23.5
HKD: 1533.5
CNY: 23.78';
preg_match_all('@(?<!CNY: )\b\d+\.\d+@is',$str,$matches);
```
## 12. 模式修飾符
下面列出了當前可用的 PCRE 修飾符。括號中提到的名字是 PCRE 內部這些修飾符的名稱。 模式修飾符中的空格,換行符會被忽略,其他字符會導致錯誤。
`i (PCRE_CASELESS)
`
如果設置了這個修飾符,模式中的字母會進行大小寫不敏感匹配。
`m (PCRE_MULTILINE)
`
默認情況下,PCRE 認為目標字符串是由單行字符組成的(然而實際上它可能會包含多行), "行首"元字符 (^) 僅匹配字符串的開始位置, 而"行末"元字符 ($) 僅匹配字符串末尾, 或者最后的換行符(除非設置了 D 修飾符)。這個行為和 perl 相同。 當這個修飾符設置之后,“行首”和“行末”就會匹配目標字符串中任意換行符之前或之后,另外, 還分別匹配目標字符串的最開始和最末尾位置。這等同于 perl 的 /m 修飾符。如果目標字符串 中沒有 "\n" 字符,或者模式中沒有出現 ^ 或 $,設置這個修飾符不產生任何影響。
`s (PCRE_DOTALL)
`
如果設置了這個修飾符,模式中的點號元字符匹配所有字符,包含換行符。如果沒有這個 修飾符,點號不匹配換行符。這個修飾符等同于 perl 中的/s修飾符。 一個取反字符類比如 [^a] 總是匹配換行符,而不依賴于這個修飾符的設置。
`x (PCRE_EXTENDED)
`
如果設置了這個修飾符,模式中的沒有經過轉義的或不在字符類中的空白數據字符總會被忽略, 并且位于一個未轉義的字符類外部的#字符和下一個換行符之間的字符也被忽略。 這個修飾符 等同于 perl 中的 /x 修飾符,使被編譯模式中可以包含注釋。 注意:這僅用于數據字符。 空白字符 還是不能在模式的特殊字符序列中出現,比如序列 (?( 引入了一個條件子組(譯注: 這種語法定義的 特殊字符序列中如果出現空白字符會導致編譯錯誤。 比如(?(就會導致錯誤)。
`e (PREG_REPLACE_EVAL)`
> Warning:This feature was DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0.
如果設置了這個被棄用的修飾符, preg_replace() 在進行了對替換字符串的 后向引用替換之后, 將替換后的字符串作為php 代碼評估執行(eval 函數方式),并使用執行結果 作為實際參與替換的字符串。單引號、雙引號、反斜線(\)和 NULL 字符在 后向引用替換時會被用反斜線轉義.
`A (PCRE_ANCHORED)
`
如果設置了這個修飾符,模式被強制為"錨定"模式,也就是說約束匹配使其僅從 目標字符串的開始位置搜索。這個效果同樣可以使用適當的模式構造出來,并且 這也是 perl 種實現這種模式的唯一途徑。
`D (PCRE_DOLLAR_ENDONLY)
`
如果這個修飾符被設置,模式中的元字符美元符號僅僅匹配目標字符串的末尾。如果這個修飾符 沒有設置,當字符串以一個換行符結尾時, 美元符號還會匹配該換行符(但不會匹配之前的任何換行符)。 如果設置了修飾符m,這個修飾符被忽略. 在 perl 中沒有與此修飾符等同的修飾符。
`S
`
當一個模式需要多次使用的時候,為了得到匹配速度的提升,值得花費一些時間 對其進行一些額外的分析。如果設置了這個修飾符,這個額外的分析就會執行。當前, 這種對一個模式的分析僅僅適用于非錨定模式的匹配(即沒有單獨的固定開始字符)。
`U (PCRE_UNGREEDY)`
這個修飾符逆轉了量詞的"貪婪"模式。 使量詞默認為非貪婪的,通過量詞后緊跟? 的方式可以使其成為貪婪的。這和 perl 是不兼容的。 它同樣可以使用 模式內修飾符設置 (?U)進行設置, 或者在量詞后以問號標記其非貪婪(比如.*?)。
> Note:在非貪婪模式,通常不能匹配超過 pcre.backtrack_limit 的字符。
`X (PCRE_EXTRA)
`
這個修飾符打開了 PCRE 與 perl 不兼容的附件功能。模式中的任意反斜線后就 ingen 一個 沒有特殊含義的字符都會導致一個錯誤,以此保留這些字符以保證向后兼容性。 默認情況下,在 perl 中,反斜線緊跟一個沒有特殊含義的字符被認為是該字符的原文。 當前沒有其他特性由這個修飾符控制。
`J (PCRE_INFO_JCHANGED)
`
內部選項設置(?J)修改本地的PCRE_DUPNAMES選項。允許子組重名, (譯注:只能通過內部選項設置,外部的 /J 設置會產生錯誤。)
`u (PCRE_UTF8)
`
此修正符打開一個與 perl 不兼容的附加功能。 模式和目標字符串都被認為是 utf-8 的。 無效的目標字符串會導致 preg_* 函數什么都匹配不到; 無效的模式字符串會導致 E_WARNING 級別的錯誤。 PHP 5.3.4 后,5字節和6字節的 UTF-8 字符序列被考慮為無效(resp. PCRE 7.3 2007-08-28)。 以前就被認為是無效的 UTF-8。
## 13.常用例子
1. 匹配see開頭you結尾之間的字符串 `/see.*you/`
~~~php
$str = "phpseegggyoulsasldkwl
phpseehhhyoulsasldkwl";
preg_match_all('/see.*you/',$str,$m);
print_r($m);
~~~
```
輸出結果:Array ( [0] => Array ( [0] => seegggyou [1] => seehhhyou ) )
```
2.
文章參考:[https://www.cnblogs.com/lilyhomexl/p/5955520.html](https://www.cnblogs.com/lilyhomexl/p/5955520.html)
其他參考資料:
PCRE 正則語法:[https://www.php.net/manual/zh/reference.pcre.pattern.syntax.php](https://www.php.net/manual/zh/reference.pcre.pattern.syntax.php)
正則表達式模式中可用的模式修飾符:[https://www.php.net/manual/zh/reference.pcre.pattern.modifiers.php](https://www.php.net/manual/zh/reference.pcre.pattern.modifiers.php)
PCRE 函數 :[https://www.php.net/manual/zh/ref.pcre.php](https://www.php.net/manual/zh/ref.pcre.php)
其他文章:[https://www.cnblogs.com/Johnson-lin/p/10875388.html](https://www.cnblogs.com/Johnson-lin/p/10875388.html)