正则表达式(四)零宽断言
前面几篇文章我们介绍了正则表达式的常用概念,包括《正反元字符》、《分支条件、分组》、《后向引用、懒惰匹配》。有了这些概念基本可以满足我们日常的使用。不过,正则提供给我们更高级的用法,让我们可以更方便的匹配复杂场景。这里介绍一下零宽断言。
零宽断言,英文zero width assertion,是最近几十年才提出来的新特性,所以有些编辑器不支持(what?几十年还叫新?因为正则的发展几乎是停滞的。前面说过,它性能一般,语法又复杂,几乎没有组织愿意给它花费心思)
查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们被称为零宽断言。零宽断言有两种:
- (?=exp)叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分,如查找I’m singing while you’re dancing.时,它会匹配sing和danc。
- (?<=exp)叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
看起来很简单对吧,所以我们来看一个例子:如何给一个很长的数字中每三位间加一个逗号(当然是从右边加起)? 为了加逗号,可以这样查找需要添加逗号的部分:
((?<=\d)\d{3})+\b
用它对1234567890进行查找结果是234567890。
思考,如果用上面的正则式匹配234567890,命中的是什么?
前面提到过怎么查找不是某个字符或不在某个字符类里的字符的方法(反义)。如果只想要确保某个字符没有出现,但并不想去匹配它时怎么办? 例如,我们想查找这样的单词:它里面出现了字母q,但是q后面跟的不是字母u,我们可以尝试这样:\b\wq[^u]\w\b匹配包含后面不是字母u的字母q的单词。
但是如果q出现在单词的结尾的话,像Iraq、Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w\b将会匹配下一个单词,于是\b\wq[^u]\w*\b就能匹配整个Iraq war。
零宽断言也提供了反义,将其中等号改成叹号:
反义零宽断言语法 | 含义 |
---|---|
(?!exp) | 匹配后面不是exp的位置 |
(?<!exp) | 匹配前面不是exp的位置 |
零宽度负预测先行断言(?!exp)断言此位置的后面不能匹配表达式exp。 例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
零宽度负回顾后发断言(?<!exp) 断言此位置的前面不能匹配表达式exp。例如(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
前面查询不是q后面没有u的单次的正则怎么写?
悄悄告诉你,是
\b\w*q(?!u)\w*\b
就是把其中的反义类改成反义断言。
来一个复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)表示什么意思?
最后看一个现实点的需求
只允许12位数字,并且其中不能出现6位连续相同数字。 例如,123456789012是允许的,而123333334567是不允许的。
思路就是整个是12位数字,某个数字后面(或前面)没有跟它一样的5个。所以这个“一样的5个”是个位置,这个位置前面的字符类型要出现12次。 这样就可以简单写出来:
^(([0-9])(?!\2{5})){12}$
更进一步,这里面由于断言最外面还有一个括号,它获得了组名1,我们可以跳过它,改成:
^(?:([0-9])(?!\1{5})){12}$
