正则表达式中的正向肯定预查和正向否定预查,之前很少用到,偶尔用用时去翻查文档或直接找一些现成的正则拿来用,导致总是记不住或者理解不清其中的意思。
最近又遇到有类似的需求,顺便把这块的东西整理出来供自己以后理解翻查。
首先,在你了解了正则表达式的基础知识及使用方式之后,再来理解这个正向预查/否定预查就很好理解。
(pattern)子匹配" id="(pattern)子匹配">(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.tech", "google","search?q=eller.tech"]
["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)子匹配。这里是不会返回给你子匹配的东西。
那有什么用呢?唯一的作用就是【额外限定正则的条件】。
比如如下数据:
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位。
- 字母:A-z
- 数字: 0-9
- 符号:x21-x2f (这里假定只允许这一部分符号出现!)
那么简单写一个正则表达式:
^[A-z0-9\x21-\x2f]{6,16}$
这个正则是从头到尾只能是字母、数字、符号出现,并限制了输入的长度必须是 6-16 位,但它不满足【同时满足】这个条件。
通过前面了解到的正向肯定预查,我们给这个正则增加限定条件:
^(?=.*[0-9])(?=.*[A-z])(?=.*[\x21-\x2f])[A-z0-9\x21-\x2f]{6,16}$
这个正则就比较完善了,实现了必须【同时满足】这个限定条件,无论是数字、字母、符号,少一个都无法完成匹配。
和前一个正则相比,基本不变,只是增加了几个正则肯定预查条件:
- (?=.*[0-9]) 需要出现数字
- (?=.*[A-z]) 需要出现字母
- (?=.*[\x21-\x2f]) 需要出现符号
以上三个附加条件,少一个都不会继续匹配,直接返回失败。也就是如果想要完成匹配,也就必须完成同时匹配的这个条件。
拿其中一个举例说明下:
语法:(?=pattern)
例子:(?=.*[0-9])
由此看出.*[0-9]
是我们匹配模式的部分,0-9
是匹配数字,是不是数字只能出现在尾部呢?
不是的,前面是 .*
代表任意数量的任意字符,也意味着可能前面可能什么都没有,但数字一定有。这就不仅限于数字一定要出现在后面,也是正向肯定预查常用的语法方式。
其他两个匹配模式也是一样的逻辑。
预查
根据前面的例子理解【预查】
预查相当于额外附加的条件,或者是在后面正式的正则模式匹配之前,先检查一遍符不符合标准才会考虑是否继续匹配。
既然是预查,也不会影响后面正则表达式的匹配位置。
这是百科给予的解释,此时再参照就非常好理解:
正向否定预查(?!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}$
但,我们这里换一种方式,通过否定预查:
意味着,我们需要找出不满足表达式的情况,然后将其列出来:
- 不是纯数字
- 不是纯字母
- 不是纯符号
只要以上三个表达式同时满足,也就相当于满足两种以上的组合。
那么得出表达式如下:
^(?!^[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)//.*$