从零开始学正则(上)二


风晓
风晓 2024-01-01 06:56:59 49528 赞同 0 反对 0
分类: 资源
接上一篇从零开始学正则(上)一

二、正则的两种模糊匹配

正则表达式是一种匹配模式,要么匹配字符(符合规则的字符),要么匹配位置(符合规则字符所在的位置)。

正则之所以强大,是因为正则能实现模糊匹配;在实际开发中,我们往往需要匹配某一范围的数据。举个贴切的例子,当验证用户输入邮箱格式是否正确,除了 @ 等固定字符以外,用户输入的具体字符我们是无法估量和统计的,精准匹配显得无能为力,也只有模糊匹配能巧妙解决这个问题。

正则表达式的模糊匹配分为横向模糊与纵向模糊两种:

2.1 横向模糊

不难理解,横向模糊表示正则匹配的字符长度是不确定的,我们可以通过正则的量词实现横向匹配。不知道大家有没有在B站看到过 233 的弹幕,233 是一个网络用语,表示大笑的意思。但因为个人输入随心的习惯,可能打出2333,233333 等不定长度的弹幕,那么我们匹配弹幕中有多少 233 大笑可以用正则这么写:

const regex = /23{2,}/;

img

这里量词 {2,}表示前面的3会出现2次或者更多次,量词后面会专门介绍,我们来试试这个正则:

const regex = /23{2,}/g; 
const str = '223 233 2323 2333';
const result = str.match(regex);
//["233", "2333"]

注意正则后面有个小写的字母 g,这是正则修饰符之一,g 为 global 的简写,表示全局匹配。若不加 g ,match 方法只会匹配第一个符合条件的字符,关于修饰符后文会详细介绍。

2.2 纵向模糊匹配

纵向模糊匹配是指具体某一位置可能有多种字符的情况,横向模糊可以用量词实现,而纵向模糊匹配可以使用字符组实现,比如:

const regex = /[abc]/;

img

这段正则表示可匹配字母 a b c 其中一个,我们来看一个简单的例子:

const regex = /a[1-3]c/g;
const str = "a0c a1c a2c a3c a4c";
const result = str.match(regex);
result //["a1c", "a2c", "a3c"]

在这个例子中我们使用了字符组 [1-3] ,它本质上与 [123] 效果相同,但因为是连贯数字所以支持范围简写。下面介绍具体介绍正则字符组。

三、正则字符组

在上一个例子中我们已经了解到字符组[123]可用范围表示法写成[1-3],这是非常有用的,设想一想,我们现在想匹配数字1-9,字母a-f,要写全的话就得这样[123456789abcdef],但通过范围表示法只用短短的[1-9a-f],是不是很奈斯:

img

现在知道了连字符 - 的作用,那么现在我们就是要匹配1 - 3其中任意字符怎么做呢?有三种写法可解决这个问题,写成[-13][13-]或者使用转义符 \ 表示 [1\-3] 即可。

3.1.排除字符组

纵向模糊匹配还存在一种情况,就是某个位置可以是除了某几个字符之外的任意字符,比如我希望是除了1-3 之外的任意字符,那么我们可以使用[^1-3]表示,注意这里使用了脱字符 ^

img

3.2.常用简写

了解了字符组范围表示法,那么想匹配数字 0 到 9 可以写成 [0-9],其实它还有一种更简单的写法\d,估计这部分是很多人常忘记的知识,我们来做个整理:

字符组 含义
\d [0-9]表示是一位数字,digit数字。
\D [^0-9]表示除数字以外的任意字符。
\w [0-9a-zA-Z_]表示数字,大小写字母和下划线,word简写,又称单词字符。
\W [^0-9a-zA-Z_],非单词字符。
\s [ \t\v\n\r\f]表示空白符。包含空格,水平制表符,垂直制表符,换行符,回车符,换页符。
\S [^ \t\v\n\r\f],非空白符。
. [^\n\r\u2028\u2029],通配符,表示除了换行符,回车符,行分隔符和段分隔符之外任意字符。

空格:顾名思义,就是我们理解的空格

水平制表符\t:类似于tab键缩进的效果,一般系统中水平制表符占8列,所以根据你按的次数占据8*N列。

垂直制表符\v:让文本从下一行开始输出,且开始的列数为\v前字符的后一列。

换行符\n:从下一行开头开始输出。

回车符\r:这里的回车不是我们理解的 enter 回车另起一行开始输出,而是回到当前行开头输出,还可能将已输入文本替换,替换这一点根据环境不同表现不同。

换页符\f:在输出\f后面文本之前,会先将当前屏幕清空,类似于先清除再输出。

行分隔符和段分隔符,找了一圈也没看到好的解释,这里还望有缘人指点。

那么如果我们想匹配任意字符,有这几种写法[/d/D][/w/W][/s/S][^],其实不难理解,以[/d/D]为例,就是匹配数字以及除了数字以外的所有字符,这不就是所有字符了吗。

img

四、正则量词

在讲述正则横向模糊匹配时已有使用量词的例子,量词表示某个字符的重复次数,我们也将常用量词做个整理:

量词 含义
  至少出现m次,最多出现n次。
  至少出现m次,没有上限。
  等价于{m,m},固定出现m次
? 等价于{0,1},要么不出现,要么出现一次。
+ 等价于{1,},至少出现1次,没有上限。
* 等价于{0,},表示出现任意次数,可以不出现,也可以任意次,包容性比?和+大。

img

4.1.贪婪匹配和惰性匹配

正则默认就是贪婪匹配,贪婪就是在量词匹配规则范围内最大限度的去匹配字符,我们来看个简单的例子:

const str = "ab abb abbb abbbb abbbbb";
const regex = /ab{2,4}/g;
const result = str.match(regex);
result //["abb", "abbb", "abbbb", "abbbb"]

img

在这个例子中,我们匹配 2-4 个字母b,你给 2 个我要,给 3 个我要,哪怕给 5 个我也要尽我所能拿 4 个,是不是很贪心。

惰性与贪婪相反,惰性匹配就是在量词匹配范围内以最小限度去匹配字符,无欲无求做人本分,我们只需要在量词后接个 ? 即是惰性匹配,看个例子:

const str = "ab abb abbb abbbb abbbbb";
const regex = /ab{2,4}?/g;
const result = str.match(regex);
result //["abb", "abb", "abb", "abb"]

img

大家会不会觉得惰性匹配情况下这个次数4是不是没意义了呢?其实并不是没意义,尽管惰性匹配是以最小2次为匹配规则,但被匹配的字符前提条件是满足 2-4 之间,4还是起到了限制条件,我们改改例子再看:

const str = "abc abbc abbbc abbbbc abbbbbc";
const regex = /ab{2,4}?c/g;
const result = str.match(regex);
result //["abbc", "abbbc", "abbbbc"]

上述例子中当匹配到字段 abbbbbc 时因为字母b已经超过范围,所以不在匹配范围内。惰性可以理解为,在匹配范围内拿最少的东西,我可以过的无欲无求,但也得过的温饱活得下去才行啊。

五、 正则多选分支

如果说横向模糊匹配和纵向模糊匹配都是一种匹配模式,那如果需要同时使用多种模式怎么办呢,这里我们就可以使用管道符 | 实现这一点,来看个简单的例子:

const str = "a0c a1c a2c a3c abc abbc abbbc abbbbc";
const regex = /a[1-3]c|ab{1,3}c/g;
const result = str.match(regex);
result //["a1c", "a2c", "a3c", "abc", "abbc", "abbbc"]

img

在这个例子中,我们使用了纵向模糊匹配和横向模糊匹配两种模式。

需要注意的是,分支匹配也是惰性匹配,即前面的匹配模式能满足,后面就不匹配了,来看个例子:

const str = "userName";
const regex = /user|userName/g;
const result = str.match(regex);
result //["user"]

这非常类似于js短路运算符中的 || ,以 a || b 为例,倘若 a 为真那么 b 就不判断了。

function fn1() {
  console.log(1);
  return true;
};

function fn2() {
  console.log(2);
  return true;
};
fn1() || fn2(); //1

我们再来个反转,前文虽说条件匹配是惰性,但这个前提也是一开始第一个条件能匹配上,但事实上,匹配角度正则还是期望能拿到更多字符串,我们再看个例子:

const str = "userName";
const regex = /Name|userName/g;
const result = str.match(regex);
result //["userName"]

哎?怎么不是匹配 Name 字段,其实站在匹配角度,正则就是拿条件一个个试,字符串的匹配规则就是从左往右的匹配,当左侧一开始无法匹配成功(user 和 Name 对应不上),正则就会想会不会其它条件能匹配上,那我就继续先尝试,结果第二个条件 useName 完美契合,所以第一个 Name 反而匹配不上了。

那假设除了第一个条件,其余条件都匹配不上呢?再比如这个例子:

const str = "userName";
const regex = /Name|userAge/g;
const result = str.match(regex);
result //["Name"]

同样还是从左往右匹配,一开始 Name 和 userName 开头无法匹配,正则同样还是继续尝试分支其它情况,结果userAge 这个条件直接匹配不上,没条件可以走了,正则这时候就会回溯,完整的拿不到吧,那继续退而求其次,匹配回溯再从第一个条件开始,拿 Name 进行部分匹配,哎,这下发现 userName 里面确实有一部分可以匹配上,于是得到了 Name。

你看,一个小小的条件匹配,里面其实藏着不少细节,我们总结下:

  • 条件匹配是惰性的,如果第一个条件能跟字符串第一个字符开始匹配上,那后续条件就不用匹配了。
  • 条件匹配也是贪婪的,如果第一个字符匹配不上,那就先放弃局部匹配,继续试试其它条件,除非都完成匹配失败,再回溯所有条件进行局部匹配。

那么到这里,我们先做个总结,大家可以看着思维导图回顾下知识点:

img

让我们来两个练习,尝试写出匹配 24 小时制的正则匹配(只包含小时分钟即可),以及匹配16进制颜色值的正则,注意,16进制颜色是支持 #dddddd 与 #ddd 两种。

我们先来解决24小时制时间匹配的正则,首先二十四小时制的时间一般是 09:30 或者 23:59 这样,小时的第一位数字可能是[0-2]三种情况之一,当为 0,1 时,第二位数字可以是[0-9]任意数字,当为 2 时第二位数字只能是 0-3 之间的数字。第三位数字只能是 0-5 之间的数字,最后一位数字只能是 0-9 之间。

整理下信息:

  • 当第一位数字为 0 1 时,第二位数字可以是[0-9]任意数字,比如 00、09、19。
  • 当第二位数字为 2 时,第二位只能是 21 22 23,固定的。
  • 第三位数字只能是[0-5],逢五进一,比如 01 59,不可能大于 5。
  • 第四位数字范围[0-9],结合第三位很好理解 00 09 50,逢九进一。

综上,我们只用对于小时的两种情况做个分支,分钟固定的范围即可,所以正则可以写成这样:

const regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/;
regex.test("00:07"); //true
regex.test("23:59"); //true

img

注意,匹配小时的分支我们使用了一对圆括号包裹,表示这是一个组,而组内包含了两个分支情况,如果不加圆括号正则解析时会将管道符 |左右两侧理解成两个分支,如下图,很明显这不是我们想要的规则:

img

其次,在正则内部开头和结尾我们分别使用了^$两个符号,这表示正则匹配时严格以字符串开头和结尾中间的内容为匹配对象,如果不加效果就是这样:

const regex = /([01][0-9]|[2][0-3]):[0-5][0-9]/;
regex.test("0000:0709");//true

上面这代正则匹配为 true 是因为字符串中间有一部分是符合规则,所以如果我们想匹配一个字段从头到尾是否符合规则,一定得记得加上 ^$ 符号限制从头到尾整个字符都得符合规则。

我们再来分析16进制颜色,提前查了下,每个字母范围均为[0-9a-fA-F],但由于颜色值可以简写,比如 #ffffff 可以简写成 #fff,所以存在 6 位与 3 位的情况,结合分组,正则可以这么写:

const regex = /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/;
regex.test("#e4393c"); //true
regex.test("#2b99ff"); //true

img

如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!

评价 0 条
风晓L1
粉丝 1 资源 2038 + 关注 私信
最近热门资源
银河麒麟桌面操作系统备份用户数据  123
统信桌面专业版【全盘安装UOS系统】介绍  117
银河麒麟桌面操作系统安装佳能打印机驱动方法  109
银河麒麟桌面操作系统 V10-SP1用户密码修改  102
最近下载排行榜
银河麒麟桌面操作系统备份用户数据 0
统信桌面专业版【全盘安装UOS系统】介绍 0
银河麒麟桌面操作系统安装佳能打印机驱动方法 0
银河麒麟桌面操作系统 V10-SP1用户密码修改 0
作者收入月榜
1

prtyaa 收益393.62元

2

zlj141319 收益218元

3

1843880570 收益214.2元

4

IT-feng 收益209.03元

5

风晓 收益208.24元

6

777 收益172.71元

7

Fhawking 收益106.6元

8

信创来了 收益105.84元

9

克里斯蒂亚诺诺 收益91.08元

10

技术-小陈 收益79.5元

请使用微信扫码

加入交流群

请使用微信扫一扫!