php正则表达式环视详解

2015年06月23日 12:11 by:网络收集

php正则表达式环视详解

1. 顺序肯定环视(?=exp)

零宽度正预测先行断言,又称顺序肯定环视,断言自身出现位置的后面能匹配表达式exp。

比如,匹配以“ing”结尾的单词前面部分(除了“ing”以外的部分):

  1. bw+(?=ingb)
以上表达式查找以下句子时,会匹配“sing”和“danc”:

I'm singing while you're dancing.

2. 逆序肯定环视(?<=exp)

零宽度正回顾后发断言,又称逆序肯定环视,断言自身出现位置的前面能匹配表达式exp。

比如,以re开头的单词的后半部分(除了re以外的部分):

  1. (?<=bre)w+b

以上表达式在查找以下句子时匹配“ading”:

reading a book

假如在很长的数字中,每3位间加1个逗号(当然是从右边加起),可以在前面和里面添加逗号的部分:

  1. ((?<=d)d{3})+b

用以上表达式对“1234567890”进行查找,结果是“,234,567,890”。这里的逗号只是匹配需要添加逗号的位置,还没有实际添加逗号。

下面这个例子同时使用这两种断言,匹配以空白符间隔的数字(再次强调,不包括这些空白符):

  1. (?<=s)d+(?=s)

前面提到过反义,用来查找不是某个字符或不在某个字符类里的字符。如果只是想要确保某个字符没有出现,但并不想去匹配它时怎么办?例如,如果想查找这样的单词——出现字母q,但是q后面跟的不是字母u。可以尝试这样:

  1. bw*q[^u]w*b

以上表达式匹配包含后面不是字母u的字母q的单词。但是如果多做几次测试就会发现,如果q出现在单词的结尾,例如Iraq、Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,如果q是单词的最后一个字符,后面的“[^u]”将会匹配q后面的单词分隔符(可能是空格、句号或其他),后面的“w*b”将会匹配下一个单词,于是以上表达式就能匹配整个Iraq fighting。

逆序肯定环视能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,解决这个问题如下所示:

  1. bw*q(?!u)w*b

3. 顺序否定环视(?!exp)

零宽度负预测先行断言,又称顺序否定环视,断言此位置的后面不能匹配表达式“exp”。例如:

1)匹配3位数字,而且这3位数字的后面不能是数字:

  1. d{3}(?!d)

2)匹配不包含连续字符串abc的单词:

  1. b((?!abc)w)+b

如果匹配的单词是c开头、t结尾,中间有一个字符,但不能是u(也就是说,整个单词不能是cut),直接用“c[^u]t”就可以了,若中间的字符不能是a或u(也就是说,整个单词不能是cat或cut),则表达式改为“c[^au]t”。

如果认真读过关于排除型字符组的章节的读者肯定会知道,这个表达式能匹配的只是cot之类的单词,因为中间的排除型字符组“[^au]”必须匹配一个字符。可是,如果还想匹配chart、conduct和court怎么办?最简单的想法是:去掉排除型字符组的长度限制,改成“c[^au]+t”。

不幸的是,这样行不通,因为这个表达式的意思是:c和t之间由多于一个“除a或u之外的字符”构成,而chart、conduct和court都包含a或u。

我们发现,其实要否定的是“单个出现的a或u”,而不仅仅是“出现的a或u”,所以才出现这样的问题。要解决这个问题,就应当把意思准确表达出来,变成“在结尾的t之前,不允许只出现一个a或u”。想到这一步,就可以用顺序否定环视(?!…)来解决。表示在这个位置向右,不允许出现子表达式能够匹配的文本,把子表达式规定为“[au]tb”(最后的“b”很重要,它出现在t之后,保证t是单词的结尾字母)。有了限制,匹配a和t之间文本的表达式就随意很多,可以用匹配单词字符的简记法“w”表示,于是整个表达式变成:

  1. c(?![au]t b)w+t

注意 这里出现的并不是排除型字符组“[^au]”,而是普通的字符组[au],因为顺序否定环视本身已经表示了否定。

进一步思考,整个匹配文本中都不能出现字符串“cat”,要怎么办呢?这个正则表达式应该是:

  1. ^(?:(?!cat).)+$

即在文本中的任意位置,都不能出现该字符串。

4. 逆序否定环视(?<!exp)

零宽度负回顾后发断言,又称逆序否定环视,可以用(?<!exp)断言此位置的前面不能匹配表达式exp。例如,前面不是小写字母的7位数字:

  1. (?<![a-z])d{7}

分析以下表达式,匹配不包含属性的简单HTML标签内的内容:

  1. (?<=<(w+)>).*(?=</1>)

以上表达式最能表现零宽断言的真正用途。(<?(w+)>)指定前缀为:被尖括号括起来的单词(比如可能是“<b>”),然后是“.*”(任意的字符串),最后是一个后缀(?=</1>)。注意后缀里的“/”,用到了前面提过的字符转义;“1”则是反向引用,引用的正是捕获的第一组,即前面(w+)匹配的内容,如果前缀实际上是“<b>”,后缀就是“</b>”。整个表达式匹配的是“<b>”和“</b>”之间的内容(再次提醒,不包括前缀和后缀本身)。

总体而言,环视相当于对“所在位置”附加一个条件,难点就在于找到这个“位置”。这一点解决了,环视就没有什么秘密可言了。

标签:正则 环视