[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 贪婪匹配的匹配过程
![](https://img.kancloud.cn/48/a5/48a5f2cb59596c60dae71dcbd493560f_938x540.png)
### 6.3 惰性匹配的匹配过程
![](https://img.kancloud.cn/6f/4e/6f4e3509c9699585a11c3720460e56e2_910x797.png)
## 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)