博文記錄
教程分享 2021-01-14 14:28:37 2234 0

正则表达式中的正向肯定预查和正向否定预查,之前很少用到,偶尔用用时去翻查文档或直接找一些现成的正则拿来用,导致总是记不住或者理解不清其中的意思。

最近又遇到有类似的需求,顺便把这块的东西整理出来供自己以后理解翻查。

首先,在你了解了正则表达式的基础知识及使用方式之后,再来理解这个正向预查/否定预查就很好理解。

(pattern)子匹配

在正则表达式中,常用到的(pattern)子匹配,用于匹配“符合正则表达式规则”后,获取其中的子结果数据,比如:

www.([\w]+).com

可以匹配到的结果是包含目标结果中的子字串的

["www.google.com", "google"]
["www.youtube.com", "youtube"]
["www.bing.com", "bing"]

而且也可以支持多个子匹配,比如,还要获取具体访问的网址 PATH 段内容

www.([\w]+).com/([\w]+)

这个正则可以 匹配到

["www.google.com/search?q=eller.top", "google","search?q=eller.top"]
["www.youtube.com/results?search_query=jj+lin", "youtube","results?search_query=jj+lin"]
["www.bing.com/search?q=eller", "bing","search?q=eller"]

简而言之,就是当你需要“获取符合正则表达式规则的数据中”提取一部分子数据时用到的。

正向肯定预查(?=pattern)

来看一段解释:

非获取匹配,正向肯定预查,在任何匹配 pattern 的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。

非获取匹配

这句话怎么理解,首先【非获取匹配】是正则的一种匹配方式,这种匹配方式匹配到结果不会在最终返回给你,参考上文的(pattern)子匹配这里是不会返回给你子匹配的东西

那有什么用呢?唯一的作用就是【额外限定正则的条件】。

比如如下数据:

213 888 1234
212 000 4567
312 000 5678
217 000 8910
213 076 4100
216 888 8910

仅获取 213 或者 212 开头的的手机号:

(?=212|213)\d{3} \d{3} \d{4}

满足上述条件基础上,当第一段为 213 时,第二段必须为 888 时的手机号:

(?=213 888|212)\d{3} \d{3} \d{4}

直接看一个常见需求,要求用户输入的密码必须同时满足包含字母、数字、符号,且为 6-16 位。

  1. 字母:A-z
  2. 数字: 0-9
  3. 符号:x21-x2f  (这里假定只允许这一部分符号出现!)

那么简单写一个正则表达式:

^[A-z0-9\x21-\x2f]{6,16}$

这个正则是从头到尾只能是字母、数字、符号出现,并限制了输入的长度必须是 6-16 位,但它不满足【同时满足】这个条件。

通过前面了解到的正向肯定预查,我们给这个正则增加限定条件:

^(?=.*[0-9])(?=.*[A-z])(?=.*[\x21-\x2f])[A-z0-9\x21-\x2f]{6,16}$

这个正则就比较完善了,实现了必须【同时满足】这个限定条件,无论是数字、字母、符号,少一个都无法完成匹配。

和前一个正则相比,基本不变,只是增加了几个正则肯定预查条件:

  1. (?=.*[0-9]) 需要出现数字
  2. (?=.*[A-z]) 需要出现字母
  3. (?=.*[\x21-\x2f]) 需要出现符号

以上三个附加条件,少一个都不会继续匹配,直接返回失败。也就是如果想要完成匹配,也就必须完成同时匹配的这个条件。

拿其中一个举例说明下:

语法:(?=pattern)

例子:(?=.*[0-9])

由此看出.*[0-9] 是我们匹配模式的部分,0-9是匹配数字,是不是数字只能出现在尾部呢?

不是的,前面是 .* 代表任意数量的任意字符,也意味着可能前面可能什么都没有,但数字一定有。这就不仅限于数字一定要出现在后面,也是正向肯定预查常用的语法方式。

其他两个匹配模式也是一样的逻辑。

预查

根据前面的例子理解【预查】

预查相当于额外附加的条件,或者是在后面正式的正则模式匹配之前,先检查一遍符不符合标准才会考虑是否继续匹配。

既然是预查,也不会影响后面正则表达式的匹配位置。

这是百科给予的解释,此时再参照就非常好理解:

预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

正向否定预查(?!pattern)

首先看一下解释:

非获取匹配,正向否定预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。

如果理解了上文的正向肯定预查,这个否定预查就是反向,可以理解为:

  • 结果必须是非 pattern 匹配到的东西
  • pattern 是什么,就要过滤掉什么
  • 想要正则匹配到的结果中不能有符合 pattern 规则的东西

直接来看例子:

要求用户输入的密码只能包含字母、数字、符号,必须是满足两种以上的组合,且长度为 6-16 位。

这里如果考虑组合方式也可以,列出 3 种组合,然后通过上文的正向肯定预查得出:

^(?=.*[0-9|A-z])(?=.*[0-9|\x21-\x2f])(?=.*[A-z|\x21-\x2f])[A-z0-9\x21-\x2f]{6,16}$

但,我们这里换一种方式,通过否定预查:

意味着,我们需要找出不满足表达式的情况,然后将其列出来:

  1. 不是纯数字
  2. 不是纯字母
  3. 不是纯符号

只要以上三个表达式同时满足,也就相当于满足两种以上的组合

那么得出表达式如下:

^(?!^[0-9]+$)(?!^[A-z]+$)(?!^[\x21-\x2f]+$)[A-z0-9\x21-\x2f]{6,16}$

这个相比上面的组合逻辑,看起来字符长度更少了些。

其实上面的表达式是为了方便理解,将 3 个条件拆开,写成了三个正向否定预查匹配,也可以合成一个:

^(?=.*([0-9|A-z]|[0-9|\x21-\x2f]|[A-z|\x21-\x2f]))[A-z0-9\x21-\x2f]{6,16}$

但长度还是没有否定预查来的短~

上文中为了方便理解,符号匹配只列举了一部分【x21-x2f】(绿色部分),完整的可见常用符号还有很多(蓝色的部分):

在正则中 ASCII 码的部分,可以用【\x+十六进制】表示,比如 \x3d 代表等于号 (=),\x20 代表空格(space),范围可以通过中 - 列举,\x21-\x2f 为上图绿色方框的部分。

最后

最终我写一个较完整的正则表达式,可以尝试理解一下:

(内容必须同时满足:大写字母、小写字母、数字、特殊符号,且长度为 6-16 位)

^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e])[\x21-\x7e]{6,16}$

(内容必须满足【大写字母、小写字母、数字、特殊符号】2 种以上的组合,且长度为 6-16 位)

^(?!^\d+$)(?!^[A-Za-z]+$)(?!^[\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]+$)[\x21-\x7e]{6,16}$

(匹配代码中的所有注释内容 // xxx)

(?<!http:|\S)//.*$
media iamge
StudioEIM - 冒险者讲习所
0:00