正则表达式教程 正则 正则表达式 教程 环视 技术 正则表达式教程五 —— 环视(零宽断言) 2017-12-25 17:17 2920 更新于 2017-12-25 17:17 环视(`lookahead`),有些也叫零宽断言,不过总感觉这名字太深奥,我喜欢叫环视。 正如它的别名一样,它的匹配是“零宽度”的,是不会占用字符的,**只匹配文本中的特定位置,所以环视表达式中匹配的内容是不会被返回的**。类似单词分界符 `\b`,锚点 `^` 和 `$` 在第三节中,已经粗略的讲了一下环视,乍看起来很简单,但是真正要弄明白却很难,而且它还有四种环视。 虽然有四种,但是理解其中一种,基本其它几种也都触类旁通了。下面就来一一介绍一下 ## 1、肯定顺序环视:(?=pattern) 首先呢,正则的执行顺序是从左至右的,那么这里的意思就是:匹配后面是 pattern 的内容 pattern 可以是确切的字符,如 a,1...,也可以是表达式,如 \d ,[a-z]... ### 例子23 ```php $s = <<<'TEXT' abc TEXT; preg_match_all('#ab(?=[a-z])#',$s,$m); echo "<pre>"; print_r($m); echo "</pre>"; /* Array ( [0] => Array ( [0] => ab ) ) */ ``` 匹配ab后面是小写字母的字符串,比如待匹配串是 ab5 ,就不能匹配。 这里只返回了 ab ,为什么呢?这就体现了上面的重点 **只匹配文本中的特定位置,所以环视表达式中匹配的内容是不会被返回的** ## 2、否定顺序环视:(?!pattern) 匹配后面不是 pattern 的内容 ### 例子24 ```php $s = <<<'TEXT' ab5 TEXT; preg_match_all('#ab(?![a-z])#',$s,$m); echo "<pre>"; print_r($m); echo "</pre>"; /* Array ( [0] => Array ( [0] => ab ) ) */ ``` 和例子23相反,匹配 ab 后面不是小写字母的字符串 ## 3、肯定逆序环视:(?<=pattern) 上面两种都是顺序(从左至右)的,逆序环视就是解决匹配前面(从右至左)的问题 匹配前面是 pattern 的内容 ### 例子25 ```php $s = <<<'TEXT' ab666 TEXT; preg_match_all('#(?<=[a-z])666#',$s,$m); echo "<pre>"; print_r($m); echo "</pre>"; /* Array ( [0] => Array ( [0] => 666 ) ) */ ``` 匹配 666 前面是小写字母的字符串,若把待匹配字符串改为 aB666 ,就不能匹配(因为 B 是大写) ## 4、否定逆序环视:(?)` 匹配前面是 `` ,`` ,`` 等这样的字符串,并将匹配到的字母捕获(因为加了小括号),以供后面使用 `(.+?)` 匹配除了 `\n` 的所有内容(因为用了 `.`),但是我用了修饰符 `s`,所以可以换行匹配,所以可以匹配 **abc** **666** `(?=\1>)` 匹配后面是 `` ,`` ,`` 等这样的字符串,那为什么表达式里面没有看见 a,m,b 这样的字母呢? 这里用了`反向引用`,仔细阅读前面几节的同学应该都知道,都有提过。 因为正则是从左至右执行的,在第一段子表达式中我们捕获了字母`([a-z])` 由于是第一个捕获(第一个小括号)的内容,所以反向引用就是 `\1`,若是第二个捕获的内容,就是用 `\2`,以此类推 那又有人问了:第一个小括号不是 `(?<=pattern)` 吗? 这就是我反复强调的【**环视表达式中匹配的内容是不会被返回的**】 那么这里为什么要用反向引用呢,直接写成和前面的一样`(?=[a-z]>)`不就行了? 这是为了标签配对,不然会匹配到前面是以 a 开头,后面却是以 m 结尾的内容,获取的内容都跨标签了,比如这样: ``` 12abc56</a> 7890234 <m>abc 666 ``` 注:在这个例子中第三段子表达式即使换成了 `(?=[a-z]>)` 也还是可以正常匹配,因为第二段子表达式我加了 `?` ,非贪婪匹配,若去掉 `?` 就会有这种问题 这个表达式可以匹配单个字母标签内的内容,却不能匹配多个字母的标签内容,如例子中的 div 有些人会说,直接在第一段内容中加个` +` 号,匹配多个字母不就行了? 这就是接下来要说的一个重要问题 首先说明,这种方法是不行的,**逆序环视只能匹配固定长度的内容**,当然顺序是可以不固定长度的,比如如下这个匹配div的例子 ### 例子28 ```php $s = <<<'TEXT' <a>12abc56</a> 7890234 <m>abc 666</m> <div>a12 b38</div> TEXT; preg_match_all('#(?<=<([a-z]{3})>)(.+?)(?=</[a-z]+>)#s',$s,$m); echo "<pre>"; print_r($m); echo "</pre>"; /* Array ( [0] => Array ( [0] => a12 b38 ) [1] => Array ( [0] => div ) [2] => Array ( [0] => a12 b38 ) ) */ ``` `(?<=<([a-z]{3})>)` 这里使用了限定符 `{3}`(第二节有讲哦),说明只匹配三个字母的标签,这样就是定长的 `(?=[a-z]+>)` 这里我没有使用反向引用,而是直接匹配多个小写字母 `[a-z]+`,这是不定长的 perl 和 python 的逆序环视是只能匹配固定长度的内容,而php 的正则套件继承的就是 perl 的正则 但是java 的 regex 包的逆序环视是可以不定长的,但长度却是有限的 因为正则是从左到右顺序执行,若逆序执行还不限制长度,那么之前执行过的文本不是每次都要再匹配?匹配次数几何倍增,不推荐也不科学。 ## 布置作业 ``` $s = <<<'TEXT' <a>abcd</a> 666 <b>efg</b> TEXT; ``` 用 【否定逆序环视和否定顺序环视】 匹配出 666