文章目录
环视
(lookaround),分为
前瞻
(Lookahead )和
后顾
(lookbehind),统称为
零宽度断言
(Lookahead and Lookbehind Zero-Length Assertions)。那什么是零宽度断言呢?正则环视实际上是有匹配字符的,但随后又立即放弃了对字符的占有或消耗。由于
环视不消耗字符串
,只返回是否匹配(存在)的结果,所以我们称为零宽度断言。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5c3a58c45e20ceec7831a8e6cd6f9f99.png)
前瞻(Lookahead )
肯定式前瞻(positive lookahead )
肯定式前瞻
是用来判断一个字符串后接某种模式的字符串。语法为:(?=子模式)
。是不是有点绕?下面我来举例说明一下。
比如,我们只要
匹配后面紧接着ing的单词,如下着色部分:
do
doing
looking
look
working
work
此时,就是肯定式前瞻
语法发挥作用的时候了。由于都是小写字母,且只要ing前面的那部分,因此我们这么写就可以了[a-z]+(?=ing)
可以看到,环视(?=ing)
里面的ing
不会被着色,也就是不会被占用,这就是我们说的环视不会占用(消耗)字符串,也就是所谓的零宽度
断言。零宽度的意思即为不消耗字符串
。
否定式前瞻(negative lookahead )
否定式前瞻
是用来判断一个字符串不后接某种模式的字符串。语法为:(?!子模式)
。现在我们要匹配的部分如下(匹配数字,但排除掉后接减号-的数字):
1+2+3-5-8
这个时候,我们只要这么写就可以了\d+(?!-)
后顾(Lookbehind )
肯定式后顾(positive lookbehind )
肯定式后顾
用来判断一个字符串前面为某种模式的字符串。语法为:(?<=子模式)
。
下面举例说明下,比如我们想要加号+后面的数字
11+22-33*444+5
这个时候,我们就需要用到肯定式后顾的语法,正则为(?<=\+)\d+
,由于加号是元字符,表示量词,所以我们要先转义一下。
否定式后顾(negative lookbehind )
否定式后顾
是用来判断一个字符串前面不为某个模式的字符串。语法为:(?<!子模式)
。
如下,我们只要着色部分的数据:
a1234
b1234
c1234
d1234
这个时候我们就要这么写(?<!c)1234
,如下图:
环顾剖析
编程语言兼容性
- 前瞻语法,
大多数语言都支持
。如C#、java、javascript、php、python等等。 - 后顾语法,
大多数语言存在差异
:
C#
支持比较全面
;
java、python、php、perl等
在后顾环视里面不能用*
、+
等不确定长度的量词;
javascript
只有到了ES2018才支持
该语法,目前只有谷歌浏览器支持。
匹配细节
- 单纯的环视语句,匹配到的其实是一个位置。如下面所示:
(?=\d)
匹配到的是数字前面(左边)的那个位置
(?!\d)
匹配到的是非数字字符前面(左边)的那个位置
(?<=\d)
匹配到的是数字字符后面(右边)的那个位置
(?<!\d)
匹配到的是非数字字符后面(右边)的那个位置
实例演练
校验必须存在某些字符
密码校验,要求:由数字、字母组成,长度为8~32之间,且必须同时包含数字、大小写字母。
思路分析:
- 对于
校验
这种类型的,我们首先要想到用来限制开头^
和结尾$
的这两个字符 - 对于校验
必须同时存在某些字符
的问题,我们要想到用肯定式前瞻(?=子表达式)
的语法 - 公式归纳:
^(?=.*子表达式1)(?=.*子表达式2)(?=.*子表达式n).+$
,一个环视的子表达式用来保证必须含有某种模式的一种情况。此处环视的子表达式的出现顺序无关,(?=.*子表达式1)(?=.*子表达式2)
和(?=.*子表达式2)(?=.*子表达式1)
这两种顺序是一样的,因为环视是零宽度断言的,一个环视执行判断完后,失败则停止,成功则返回到原来开始环视的那个位置,如果有其他环视语句的话,则继续执行下一个环视,以此类推,然后执行剩下的会消耗字符的主体匹配正则。一个环视相当于编程语言里面的一个if判断。但是值得注意的是,这些环视的子表达式必须放置开头位置
,因为正则是从左到右开始匹配的。
接下来,我们便来看看这个怎么写。
首先,数字可以写成\d
或[0-9]
,这是等价的写法,字母可以写成[a-zA-Z]
,长度为8~32,可以写成{8,32}
,由此,我们首先可以得到如下正则
[0-9a-zA-Z]{8,32}
因为是用来校验的,所以我们在前后需要加上^
和$
,用来限制起始位置,可得
^[0-9a-zA-Z]{8,32}$
由于需要限制必须含有数字,因此要加上(?=.*[0-9])
,得
^(?=.*[0-9])[0-9a-zA-Z]{8,32}$
必须含有小写字母,继续加上(?=.*[a-z])
,得
^(?=.*[0-9])(?=.*[a-z])[0-9a-zA-Z]{8,32}$
必须含有大写字母,继续加上(?=.*[A-Z])
,得
^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{8,32}$
,这个就是对应于该题的完整的正则。
见过有些人会把环视写在^
前面,如(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])^[0-9a-zA-Z]{8,32}$
对于校验来讲,这也是没问题的。
但,千万不要写成这样^[0-9a-zA-Z]{8,32}(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])$
,因为这样永远也匹配不成功!
上面的正则图形化后为:
如上图所示,由于前面的[0-9a-zA-Z]{8,32}
是会消耗(占用)字符串的,又正则是从左到右匹配的,因此[0-9a-zA-Z]{8,32}
会先执行
,从左到右先消耗掉8到32个字符
,这个时候,后面的环视子表达式已经没有可用的字符了(此时只剩下个结束位置
),所以会导致正则表达式匹配失败!
下面我们来解释一下肯定式环视后置的匹配:
^[0-9a-zA-Z]{8,32}(?=.*$)(?=.*$)(?=.*$)$
或^[0-9a-zA-Z]{8,32}(?=.*$)$
匹配如下,是可以成功的。
我们先来看一种只有环视(?=.*[0-9])
的情况,然后长度暂时改为7,同时去掉限定结尾符号$
,如^[0-9a-zA-Z]{7}(?=.*[0-9])
,这个时候,是可以匹配到的,如下:
可以看到,此时匹配到了前面的7个字符,且第7个字符后面只要存在数字就会被匹配到。接下来,我们增加限制^[0-9a-zA-Z]{7}(?=.*[0-9])(?=.*[a-z])
,此时,第7个字符后面需要同时存在数字和小写字母的才会被匹配:
我们再增加限制,^[0-9a-zA-Z]{7}(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])
,此时,第7个字符之后,必须同时存在数字、大小写字母才会被匹配:
从上面的匹配可以得到结论
:当肯定式前瞻(?=)
放到一个会消耗字符串的正则后面,其实是需要等到该正则先匹配消耗字符,然后环视是从那个最后被消耗的字符的下一个位置开始从左到右环视的。
校验必须不存在某些字符
验证码校验,由数字和字母组成,长度为4~6位,且不能存在数字0和字母oO。
思路分析:
- 对于校验必须不存在某些字符的需求,可以用
否定式前瞻(?!子表达式)
的语法 - 公式归纳:
^(?!.*子表达式1)(?!.*子表达式2)(?!.*子表达式n).+$
首先,数字可以写为\d
或[\d]
或[0-9]
,字母为[a-zA-Z]
,长度为{4,6},得正则
^[0-9a-zA-Z]{4,6}$
,但不能存在数字0,得^(?!.*0)[0-9a-zA-Z]{4,6}$
,不能存在字母o和O,得
^(?!.*0)(?!.*[oO])[0-9a-zA-Z]{4,6}$
也可以写成
^(?!.*[0oO])[0-9a-zA-Z]{4,6}$
匹配url的查询条件的值
如下,我们需要写一个正则,把着色部分的值取出来
https://www.baidu.com/s?id=test&name=bill&age=22
分析可得,每个值都是在=号的后面,且是以&分割。因此,我们可以利用肯定式后顾(?<=子表达式)
语法来匹配,即(?<==)[^&]+
校验不以某种字符串结尾
如下,我们需要匹配以下被着色的数据,也就是要排除掉以gif和png结尾的数据。
123.jpg
123.gif
123.json
123.png
123.bmp
这个时候,我们可以考虑使用否定式后顾(?<!子表达式)
的语法,如^.+(?<!gif|png)$
微信公众号: