说明

本文基于(.net)正则表达式30分钟入门教程,增加了一些自己的理解。

正则表达式

定义

在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

通配符

Windows系统中?*可以用于查找文档,这个叫做通配符。

元字符

代码 说明
. 匹配除换行符\t以外的任意字符
\w 匹配字母或数字或下划线(或汉字, .net专有)
\s 匹配任意的空白符(任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格)
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

字符转义

有特殊意义的字符需要转义,包括但不局限于.*\

重复

代码 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

字符类

[]框起来,比如[aeiou]表示元音字母、[.?!]表示标点符号。

分支条件

|隔开不同的规则。

分組

()框起来进行重复字符之类的限定。

以下是一個利用了分组和分支条件的例子(匹配有效IP)

/^((2[0-4]\d|25[0-5]|1\d{2}|[1-9]\d|[1-9])\.){3}(2[0-4]\d|25[0-5]|1\d{2}|[1-9]\d|[1-9])$/

反义

用于查找不属于某个某些字符类的字符。

代码 说明
\W 匹配任意不是字母,数字,下划线,汉字(.net)的字符
\S 匹配任意不是空白符的字符(任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格)
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

例如,\S+匹配不含空白字符的字符串,<a[^>]+>匹配尖括号括起来的以a开头的字符串。

后向引用

使用小括号指定一个子表达式之后,这个分组会自动拥有一个组号,规则为从左向右,以分组的左括号为标志,第一个出现的为1,第二个为2,以此类推。

用反斜线加组号表示引用此组,如\1

这样,匹配有效IP的例子就可以缩减如下

/^((2[0-4]\d|25[0-5]|1\d{2}|[1-9]\d|[1-9])\.){3}\2$/

类似,我们可以指定分组的组名。使用这样的语法(?<Word>\w+)或者把尖括号换成引号(?'Word'\w+),这样就把组名称指定为Word了,可以用\k<Word>或者\k'Word'

ps,组名在javascript中不可用。

另外一种,(?:exp)不改变正则表达式的处理方式,不捕获到某个组里面,不分配组号。引用正则表达式中的(?:exp)这样匹配表达式,有什么意义,作用一般来说是为了节省资源,提高效率。

代码 说明
(exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?’name’exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号

PHP中的后向引用

PHP中的后向引用带编号的子组一定是从被捕获的子组开始编号的,从1开始,那么断言中的子组默认是不被捕获的。

如果想要重复利用断言中的子组,那么一定记得给子组起别名(?<Word>exp)

例如,/(?<=<(?<test>div)>).*?(?=<\/\k<test>>)/x在PHP中是可以匹配的,/(?<=<(div)>).*?(?=<\/\1>)/x则无法匹配到1号子组,是一个无效的正则。

零宽断言

用于查找在某些内容(但不包括这些内容)之前或之后的东西,也就是说它们像\b^$用于指定一个位置,这个位置应该满足一定的条件(断言),称为零宽断言

(?=exp)称为零宽度正预测先行断言断言自身出现的位置的后面能匹配表达式exp

(?<=exp)称为零宽度正回顾后发断言断言自身出现的位置的前面能匹配表达式exp

例如,(?<=\s)\d+(?=\s)匹配以空白符间隔的数字(再次强调,不包括这些空白符)

负向零宽断言

用于确保某个字符没有出现,但并不想去匹配它,类似零宽断言。

代码为(?=exp)(零宽度正预测先行断言断言自身出现的位置的后面能匹配表达式exp)和(?<=exp)(零宽度正回顾后发断言断言自身出现的位置的前面能匹配表达式exp)。

例如,(?<=<(\w+)>).*(?=<\/\1>)用于匹配不包含属性的简单HTML标签内里的内容

代码 说明
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp后面的位置
(?!exp) 匹配后面跟的不是exp的位置
(?<!exp) 匹配前面不是exp的位置

特别的,零宽断言也是不捕获到组里面的,所以也没有组号

PHP中的后瞻断言

PHP中,后瞻断言(?<=exp)(?<!exp)中的exp必须是固定长度,或者多个顶级可选分支必须是固定长度。

例如:(?<!foo)bar(?<=bullock|donkey)是PHP中合法的正则表达式,而(?<!dogs?|cats?)则会引发一个错误 Compilation failed: lookbehind assertion is not fixed length

详细可以参见PHP Manual中正则表达式的部分

注释

小括号的另外一种用途是通过语法(?#comment)来包含注释。例如,2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)

如果包含注释的话,最好启用忽略模式里面的空白符选项,这样在编写表达式时能任意添加空格、Tab、换行,而实际使用时这些都被忽略掉。启用这个选项后,在#后面到这一行结束的所有文本都当成注释被忽略掉。

例如,如下的一个表达式:

      (?<=    # 断言要匹配的文本的前缀
      <(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
      )       # 前缀结束
      .*      # 匹配任意文本
      (?=     # 断言要匹配的文本的后缀
      <\/\1>  # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
      )       # 后缀结束

PHP中一般使用模式修饰符x来表示上述这种换行的注释。

贪婪和懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以a.*b为例,它将匹配最长的以a开始,以b结束的字符串。如果用来匹配aabab的话,结果将是整个字符串aabab,称为贪婪匹配。

有的时候,我们需要匹配尽可能少的字符。前面给出的限定符都可以被转化成懒惰匹配模式,只要后面增加一个?。这样.*?就意味着匹配任意数量的重复,但在整个匹配成功的前提下使用最少的重复

a.*?b匹配最短的,以a开始,以b结束的字符串。如果应用于aabab的话,匹配到的结果将为aab(1-3)和ab(4-5)。

比贪婪和懒惰更高的原则

The match that begins earliest wins.

最先开始的匹配拥有最高的优先权。

上述匹配中,第1个匹配是从第1个字符开始而不是从第2个字符开始的原因就是上述规则。

懒惰限定符

代码 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

模式

以下列举几个常见的模式:

模式 PCRE 说明
i PCRE_CASELESS 大小写不敏感匹配
s PCRE_DOTALL 点号元字符匹配所有字符,包含换行符。如果没有这个修饰符,点号不匹配换行符
m PCRE_MULTILINE 默认情况下,PCRE 认为目标字符串是由单行字符组成的(然而实际上它可能会包含多行),当这个修饰符设置之后,“行首”和“行末”就会匹配目标字符串中任意换行符之前或之后
x PCRE_EXTENDED (注释用)模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略
u PCRE_UTF8 utf-8字符匹配最好加上,否则会截断

平衡组(条件子组)

PHP中,条件子组主要是以下两种:

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

表示的意义如下:

如果条件满足,使用 yes-pattern,其他情况使用 no-pattern(如果指定了)。 如果有超过 2 个的可选子组,会产生给一个编译期错误。

condition内如果是数字n,表示第n个捕获组的条件,如果满足条件,那么执行yes,否则执行no或者pass。 下述表达式匹配一个没有括号的或者闭合括号包裹的字符序列

( \( )? [^()]+ (?(1) \) )

condition内如果是条件字符串(R),那么表示递归,这个后面再说。

condition内如果不是上述两种,那么必须是一个断言。 下述表达式匹配两种格式的字符串:dd-aaa-dd 或 dd-dd-dd。aaa 代表小写字母, dd 是数字

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

递归

待完善 http://php.net/manual/zh/regexp.reference.recursive.php