Home >Backend Development >PHP Tutorial >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 里支持两种正则分别是 POSIX 和 PCRE 。自 PHP 5.3.0起,POSIX 正则表达式扩展被废弃。所以下文讨论的都是基于 PCRE 模式。可点击查看有关与 POSIX 正则表达式的不同和与 perl 的不同之处。
当使用 PCRE 函数 的时候,模式需要由分隔符闭合包裹。分隔符可以使任意非字母数字、非反斜线、非空白字符。经常使用的分隔符是正斜线 / 、hash符号 # 以及取反符号 ~ 。下面的例子都是使用合法分隔符的模式。
/foo bar/#^[^0-9]$#+php+%[a-zA-Z0-9_-]%
如果分隔符需要在模式内进行匹配,它必须使用反斜线进行转义。如果分隔符经常在模式内出现,一个更好的选择就是是用其他分隔符来提高可读性。例:
/http:\/\//#http://#
正则表达式的威力源于它可以在模式中拥有选择和重复的能力。一些字符被赋予特殊的含义,使其不再单纯的代表自己,模式中的这种有特殊涵义的编码字符 称为元字符。
共有两种不同的元字符:一种是可以在模式中方括号外任何地方使用的,另外一种是需要在方括号内使用的。
在方括号外使用的元字符如下:
/ | 一般用于转义字符 |
^ | 断言目标的开始位置(或在多行模式下是行首) |
$ | 断言目标的结束位置(或在多行模式下是行尾) |
. | 匹配除换行符外的任何字符(默认) |
[ | 开始字符类定义 |
] | 结束字符类定义 |
| | 开始一个可选分支 |
( | 子组的开始标记 |
) | 子组的结束标记 |
? | a:作为量词,表示 0 次或 1 次匹配。b:位于量词后面用于改变量词的贪婪特性。 |
* | 量词,0 次或多次匹配 |
+ | 量词,1 次或多次匹配 |
{ | 自定义量词开始标记 |
} | 自定义量词结束标记 |
模式中方括号内的部分称为“字符类”。 在一个字符类中仅有以下可用元字符:
\ | 转义字符 |
^ | 仅在作为第一个字符(方括号内)时,表明字符类取反 |
- | 标记字符范围 |
示例:
反斜线 \ 有四种用法,详细可点击 转义序列(反斜线)
【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】一些简单的断言。一个断言指定一个必须在特定位置匹配的条件,它们不会从目标字符串中消耗任何字符。反斜线断言包括:
* | 重复零次或更多次,等价于 |
+ | 重复一次或更多次,等价于 |
? | 重复零次或一次,等价于 |
{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
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} 。
竖线字符用于分离模式中的可选路径。比如模式 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 位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。
使用分枝条件时,要注意各个条件的顺序
正则表达式在不同的模式修饰符下匹配出的结果有可能不相同。它的语法是 :(?修饰符)
比如,(?im) 设置表明多行大小写不敏感匹配。同样可以用它来取消这些设置,比如 (?im-sx) 设置了 “PCRE_CASELESS”,”PCRE_MULTILINE”,但是同时取消了 “PCRE_DOTALL” 和 “PCRE_EXTENDED”。如果一个字母即出现在 - 之前, 也出现在 - 之后,这个选项被取消设置。
下面紧例举简单的示例,想要了解更多可点击 内部选项设置 和 模式修饰符
示例:/ab(?i)c/ 仅仅匹配 ”abc” 和 ”abC”
子组通过圆括号分隔界定,并且它们可以嵌套。
示例:
字符串:"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