theme: cyanosis
highlight: atom-one-dark
本文正在参加「金石计划 . 瓜分6万现金大奖」
王志远,微医前端技术部
学习脉络
正则无论是用来获取某段内容,还是看是否匹配某个规则,说到底其实就是对文本信息的获取;对一个庞大东西的处理思路就像做递归
- 找到最小单元
- 设定重复
这样就能避免被大数据量搞到晕头转向了,文本也一样,本质是字符,对字符处理的最小单元自然是单个字符,而重复规模则用量词进行控制,这就迎来了本文的主角团:特殊单字符/字符组和量词。
字符组决定选择单个字符的规则;量词则决定这个选中规则要处理多少个字符。
让我们详细展开,就会发现很多像\d
、\w
之类的老朋友。
特殊单字符(字符组)
基础使用
对于单字符选择而言,在正则中的术语被称为字符组,接下来我们都会用这个术语,但不要被迷惑,它并不是匹配一组数据,而只是匹配一组数据中的一个字符,这点很关键。
语法: [xxx]
匹配规则: 目标文本需包含【任意一个包含在括号中的元素】
**获取信息规则:**将获取第一个【任意一个包含在括号中的元素】
举例: /[abc]/
这个正则将匹配 a、b、c 中的任何一位且只有一位,默认匹配第一位,使用测试平台会得到如下结果
取反逻辑
正常使用是范围内选取,不过也会出现【除了这些之外】的范围选取,这时就需要用到取反了
语法: [^xxx]
匹配规则: 目标文本需包含【任意一个不包含在括号中的元素】
**获取信息规则:**将获取第一个【任意一个不包含在括号中的元素】
举例: /[^abc]/
这个正则将匹配非 a、b、c 中的任何一位且只有一位,默认匹配第一位,使用测试平台会得到如下结果
常用字符组
八二原则同样适用在字符组中,有很多常见的匹配规则,没必要重复写,于是正则就把这些特殊字符组分别取了个别名
主要分为如下【3+1】概念,三对+一个
使用符号 | 规则 | 对应字符组 |
---|---|---|
\d | 单个数字 | [0-9] (digit 数字) |
\D | 单个任意非数字 | [^0-9] |
\w | 单个任意数字字母下划线 | [0-9a-zA-Z_] 数字字符下划线 |
\W | 单个任意非数字字母下划线 | [^0-9a-zA-Z_] 非单词字符 |
\s | 单个任意空白符 | [ \t\v\n\r\f] 表示空白符 (空格、水平制表符、垂直制表符、换行符、回车符、换页符) |
\S | 单个任意非空白符 | [^ \t\v\n\r\f] |
. | 单个除换行符外任意字符 | [^\n\r\u2020\u2029] 通配符 除换行符、回车符、行分隔符、段分隔符外任意字符 |
其他常用字符组
对应字符组 | 规则 |
---|---|
[\d\D] | [\s\S] | [\w\W] | [^] | 任意字符 |
空白字符
这类字符比较特殊,单独拎出来,方便后期直接使用
到此,我们也就知道了如何去匹配单个字符,这就是我们的“最小规模”,但一个个匹配肯定是不行,那正则将冗长到难以忍受,所以我们还需要重复,这就是量词的作用。
量词
这个没有很复杂的东西,唯一要注意的是,正则默认是只匹配一次的,即一次匹配完后就算后文还有符合的内容也不再获取,这涉及到修饰符g
,后面再补充;
看案例就行,我们还是以[abc]
作为字符组最小单元来演示。
*号:0-n 次
正则: /[abc]*/
匹配规则: 目标文本无需包含【任意一个包含在括号中的元素】,此匹配规则一定成立
**获取信息规则:**将获取第一段【包含在括号中且连续的元素组】
+号:1+n 次
正则: /[abc]+/
**获取信息规则:**将获取第一段【包含在括号中且连续的元素组】
匹配规则: 目标文本需包含【至少一个包含在括号中的元素】
?号:0 次或 1 次
正则: /[abc]?/
**匹配规则:**此匹配规则一定成立
**获取信息规则:**将获取第一个【任意一个包含在括号中的元素】
{}符号:精确控制次数
上面其实都属于特殊案例,我们可以通过{}
精确控制匹配次数,主要有三个用法
- {m}:必须出现 m 次
- {m, n}:可以出现 m-n 次
- {m,}:至少出现 m 次
我们对这三种情况进行演示,动图如下
至此,我们就完成了对量词规则的学习
量词模式
对于量词而言,既然存在一个范围,那就存在是取最大值还是最小值的分歧,计算机是死板的,一定要明确的规则才能正确运行,这就涉及到正则模式的设定,在正则中对于这个问题存在三种模式
- 贪婪模式:默认,会尽可能匹配多的内容
- 懒惰模式:量词后面加个
?
,会尽可能少匹配内容 - 独占模式:量词后面加个
+
,不触发回溯动作
举例见模式区别
测试用例: aaabb
测试正则:
- 贪婪模式:
/a*/
- 懒惰模式:
/a*?/
贪婪模式:/a*/
匹配过程:
匹配结果:
对应输出结果: ['aaa','','','']
懒惰模式: /a*?/
匹配过程:
匹配结果:
对应输出结果: ['','a','','a','','a','','','']
补充案例
特殊的模式:独占模式
贪婪和懒惰很好理解,面向的是量词的范围不确定;量词如果是属于一个不定范围的话(如 1-3),贪婪模式会默认多的去匹配;而懒惰模式则会尽可能少的去匹配。
而独占模式很特殊,它面向的是回溯问题;在量词的范围不确定情况下,无论是贪婪模式还是懒惰模式,都很有可能发生回溯现象使得匹配成功,而独占模式下则不会发生回溯,会直接返回匹配失败。
**注意:**很多地方是不支持此模式的,需要安装regex
模块
下面是python
中的处理方式
// 先安装 regex 模块 pip install regex
import regex
regex.findall(r'xy{1,3}z', 'xyyz') # 贪婪模式
regex.findall(r'xy{1,3}?z', 'xyyz') # 懒惰模式
regex.findall(r'xy{1,2}+yz', 'xyyz') # 独占模式
回溯现象
回溯是指量词的范围不确定情况下,正则引擎会
- 在贪婪模式下,尽可能多的匹配,一旦发生不匹配,就回退跳出此字符组的处理,改为处理下一个字符组,尝试是否匹配
- 在懒惰模式下,尽可能少的匹配,改为处理下一个字符组,一旦发生不匹配,就回退跳出此字符组的处理,尝试是否匹配
回溯案例分析
测试用例: xyyz
测试正则:
- 贪婪模式:
/xy{1,3}z/
- 懒惰模式:
/xy{1,3}?z/
- 独占模式:
/xy{1,3}+z/
贪婪模式下回溯现象
在匹配时,y{1,3}
会尽可能长的匹配
- 在完成匹配
xyy
时,会继续尝试匹配第三个y
,但匹配到了z
- 失败了,于是正则就会向前回溯吐出
z
- 然后用正则中的
z
去匹配,匹配成功 - 处理结束
懒惰模式下回溯现象
在匹配时,y{1,3}?
会尽可能短的匹配
- 在完成匹配
xy
时,就会结束这个字符组的匹配 - 然后用
z
去匹配,匹配到了y
- 失败了,于是正则就会向前回溯吐出
y
- 然后用正则中的
y{1,3}?
去匹配,匹配成功,会再一次结束这个字符组的匹配; - 然后用
z
去匹配,匹配成功 - 处理结束。
独占模式下回溯逻辑的处理
在匹配时,y{1,3}
会尽可能长的匹配,但一失败,不会回溯,而是会返回失败
- 在完成匹配
xyy
时,会继续尝试匹配第三个y
,但匹配到了z
- 失败了,直接返回失败
正则回溯引发的血案
这里我们挑选一个比较出名的,是阿里技术微信公众号上的发文。Lazada 卖家中心店铺名 检验规则比较复杂,名称中可以出现下面这些组合:
- 英文字母大小写;
- 数字;
- 越南文;
- 一些特殊字符,如“&”,“-”,“_”等。
阿里负责的开发在开发过程中使用了正则来实现店铺名称校验,如下所示:
^([A-Za-z0-9._()&'\- ]|[aAàÀảẢãÃáÁạẠăĂằẰẳẲẵẴắẮặẶâÂầẦẩẨẫẪấẤậẬbBcCdDđĐeEèÈẻẺẽẼéÉẹẸêÊềỀểỂễỄếẾệỆfFgGhHiIìÌỉỈĩĨíÍịỊjJkKlLmMnNoOòÒỏỎõÕóÓọỌôÔồỒổỔỗỖốỐộỘơƠờỜởỞỡỠớỚợỢpPqQrRsStTuUùÙủỦũŨúÚụỤưƯừỪửỬữỮứỨựỰvVwWxXyYỳỲỷỶỹỸýÝỵỴzZ])+$
可以简化为
^([符合要求的组成 1]|[符合要求的组成 2])+$
需要留意的是,正则中有个加号(+),表示前面的内容出现一到多次,进行贪婪匹配, 这样会导致大量回溯,占用大量 CPU 资源,引发线上问题,我们只需要将贪婪模式改成独 占模式就可以解决这个问题。
总结
在正则的元字符中,根据【最小规模+重复】思路
- 通过字符组我们得知了如何设定选择单个字符的规则
- 通过量词我们将单个字符的处理范围扩大化
- 通过量词模式我们解决了范围的歧义问题
让我们用一个题目来强化学习吧!
文末思考
手机号的组成规则
- 第 1 位固定为数字 1;
- 第 2 位可能是 3,4,5,6,7,8,9;
- 第 3 位到第 11 位我们认为可能是 0-9 任意数字。
请写出匹配正则
参考答案:/^1[3-9]\d{9}&/
暂无评论内容