学会正则表达式

227 阅读6分钟

一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。

基本匹配

前面说到正则表达式的组成,一个最简单的正则表达式:what,它表示一个规则:由字母 w开始,接着是 h,然后 a,最后 t

let reg = new RegExp('what');

reg.test(`what's your name?`);	// true
let reg1 = new RegExp(/what/);

reg1.test(`what's your name?`);	//	true
reg1.test(`What's your name?`);	//	false

可以看到正则表达式大小写敏感。我们之后也会说到如何去处理大小写敏感这个问题。

元字符

正则表达式的精彩部分-元字符,元字符不代表他们本身的意思,正则表达式赋予了它们一些特殊的含义在其中:

元字符 描述
. 英文句号匹配任意单个字符,除了换行符
[] 字符种类。匹配方括号内的任意字符。
[^] 否定的字符种类。匹配除了方括号内的任意字符
* 匹配>=0个重复的在*号之前的任意字符
+ 匹配>=1个重复的在+ 号前的字符
? 标记?之前的字符为可选
{n,m} 匹配num个大括号之前的字符(n<=num<=m)
(xyz) 字符集,匹配与 xyz 完全相等的字符串
| 或运算符,匹配符号前或后的字符
\ 转义字符,用于匹配一些保留的字符[](){}.*+?^$\|
^ 从开始行进行匹配,与方括号内的[^]代表的意义不同
$ 从末端开始匹配

2.1 点运算符

如上所说,.匹配任意字符,但不匹配换行符:

let text = 'password1234';
let point = new RegExp('/./g');


text.match(/./g);	//["p", "a", "s", "s", "w", "o", "r", "d", "1", "2", "3", "4"]
text.match(point);	// 与上述结果相同
point.test(text);	// true

2.2 字符集

在正则表达式中,方括号[] 用来指定一个字符集。在方括号中使用连字符 - 来指定字符集的范围。在方括号中的字符集不关心顺序:

let text = "The car parked in the garage."
text.match('[tT]he');	//["The", index: 0, input: "The car parked in the garage.", groups: undefined]
text.match(/[tT]he/g);	//["The", "the"];
// 效果同或(|)符号 -  注意写法
text.match(/(t|T)he/g);	//["The", "the"];

方括号内的句号就表示句号

let text = "The car parked in the garage."
text.match("ge[.]");	//["ge.", index: 26, input: "The car parked in the garage.", groups: undefined]

text = "The car parked in the garage"
text.match("ge[.]");	// null

2.2.1 否定字符集

一般来说^ 表示一个字符串的开头(下文会提到这种用法),但它用在一个方括号的开头的时候,它表示这个字符集是否定的。例如:

let text = "The car parked in the garage.";
let square = new RegExp('[^c]ar');

text.match(square);	//["par", index: 8, input: "The car parked in the garage", groups: undefined]
text.match(/[^c]ar/g);	//["par", "gar"]

2.3 重复次数

后面跟着元字符+ ,* 以及? 的,是用来指定匹配子模式的次数。这些元字符在不同的情况下代表着不一样的意思

2.3.1 * 元字符

* 元字符匹配在 * 之前出现**大于等于0次(>=)**的字符。例如,表达式a* ,匹配以 0个 或更多个a开头的字符,因为有 0个 这样的条件,其实也就匹配了所有的字符:

let text = "The car parked in the garage.";
    
text.match(/[a-z]*/g);	//(14) ["", "he", "", "car", "", "parked", "", "in", "", "the", "", "garage", "", ""]
text.match(/(c)*ar/g);	//["car", "ar", "ar"]

*元字符以及.元字符搭配可以匹配所有的字符.**和表示匹配空格的符号\s连起来用, 如表达式\s*cat\s*匹配 0个 或更多个空格开头和 0个 或更多个空格结尾的cat字符串:

let text = "The fat cat sat on the concatenation.";

text.match(/\s*cat\s*/g);	//[" cat ", "cat"]

2.3.2 + 元字符

+元字符匹配+之前的出现大于等于1次(>=1) ,例如:

let text = "The fat cat sat on the mat.";

text.match(/c.+t/g);	//["cat sat on the mat"]

2.3.3 ?元字符

?元字符表示在 ? 之前的符号字符为可选,即出现 0次1次 。例如:

let text = "The car is parked in the garage.";

text.match(/[T]?he/g);	// ["The", "he"]

2.4 {}

在正则表达式中,{} 是一个量词,常用来表示一个或一组字符可以出现的次数:

let text = "The number was 9.9997 but we rounded it off to 10.0.";

text.match(/[0-9]{2,3}/g);	//["999", "10"]

匹配的是最少两位,最多三位的0~9的数字。

如果省略第二个参数,则是匹配至少第一个参数的0~9的数字,上述就是匹配至少2位0~9的数字:

let text = "The number was 9.9997 but we rounded it off to 10.0.";

text.match(/[0-9]{2,}/g);	//["9997","10"]

注意⚠️这里是省略第二个参数,但是参数之间的,还在。如果此{} 里面只有一个参数,那代表的是固定重复次数的匹配:

let text = "The number was 9.9997 but we rounded it off to 10.0.";

text.match(/[0-9]{3}/g);	//["999"]

2.5 | 运算符

表示逻辑或,用来判断条件:

let text = "The car is parked in the garage.";

text.match(/(t|T)he/g);	//["The", "the"]

2.6 (...) 特征标群

特征标群是一组写在 (...) 中的子模式。像之前所提到的{} 是用来表示前面一个字符出现指定次数。但如果在{}前加入特征标群则表示整个标群内的字符出重复 N 次。例如表达式 (ab)* 匹配连续出现 0 或更多个 ab .

我们还可以在() 中用或字符 | 表示或。比如,(c|g|p)ar 匹配 cargarpar

let text = "The car is parked in the garage.";

text.match(/(c|p|g)ar/g);	//["car", "par", "gar"]

2.7 转码字符串

反斜线 \ 在表达式中用于转码紧跟其后的字符. 用于指定 { } [ ] / \ + * . $ ^ | ? 这些特殊字符. 如果想要匹配这些特殊字符则要在其前面加上反斜线 \.

例如 . 是用来匹配除换行符外的所有字符的. 如果想要匹配句子中的 . 则要写成 \. 以下这个例子 \.?是选择性匹配.

let text = "The fat cat sat on the mat.";

text.match(/(f|c|m)at\.?/g);	//["fat", "cat", "mat."]

2.8 锚点

在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,$ 指定结尾:

2.8.1 ^

^ 符号用来检查匹配的字符串是否在所匹配字符串的开头。

let text = "The car is parked in the garage.";

text.match(/^(t|T)he/g);	//["The"]

可以看到这次只匹配到了一个符合条件的字符The,这是因为针对要匹配的字符,只在头部进行匹配是否符合表达式的条件。

2.8.2 $

^ 原理相似,$ 符号用来匹配字符是否是最后一个:

let text = "The fat cat sat on the mat.";

text.match(/((f|c|m)at\.?)$/g);	//["mat."]

这次只匹配了字符串最后的符合正则表达式条件的字符。

3. 简写字符集

在上述示例中,我们看到过 [0-9] [a-z] 这样的表达式,分别代表着匹配数字,匹配所有的小写字母,在实际的生活中我们会经常使用这样的匹配条件,为了使用方便,正则表达式中内置了一些简写的字符集来表达此类条件:

简写 描述
. 除换行符外的所有字符
\w 匹配所有字母数字,等同于[a-zA-Z0-9_] (小写字母/大写字母/数字/下划线)
\W 匹配所有非字母数字,及符号,等同于[^\w]
\d 匹配数字: [0-9]
\D 匹配非数字: [^\d]
\s 匹配所有空格字符:[\t\n\f\r\p{Z}]
\S 匹配所有非空格字符: [^\s]
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\p 匹配 CR/LF(等同于\r\n),用来匹配 DOS行终止符

4. 零宽度断言(前后预查)

先行断言和后发断言都属于非捕获簇(不捕获文本,也不针对组合进行计数)。先行断言用于判断所匹配的格式是否在另一个确定的格式之前,匹配结果不包含该确定格式(仅作为约束):

符号 描述
?= 正先行断言-存在
?! 负先行断言-排除
?<= 正后发断言-存在
?<! 负后发断言-排除

4.1 ?=...正先行断言

?=... 正先行断言,表示第一部分表达式之后必须跟着?=定义的表达式。

返回结果只包含满足匹配条件的第一部分表达式。定义一个正先行断言要使用()括号。在括号内部使用一个问号和等号(?=...) .

正先行断言的内容写在括号中的等号后面:

let text = "The fat cat sat on the mat.";

text.match(/(t|T)he(?=\sfat)/g);	//["The"]
//让我们看一下不加正弦断言的结果是:
text.match(/(t|T)he/g);	//["The", "the"]

可以很明显的分析出来加了正先行断言所带来的结果的变化.

4.2 ?!...负先行断言

负先行断言?!用于筛选所有撇配结果,筛选条件位其后不跟随着断言中定义的格式。与正先行断言的区别就是?=问号后的符号的改变为 ?!

let text = "The fat cat sat on the mat.";

text.match(/(t|T)he(?!\sfat)/g);	//["the"]

4.3 ?<=... 正后发断言

正后发断言,使用?<=... 用于筛选所有皮配结果,筛选条件为其前不跟随断言中定义的格式:

let text = "The fat cat sat on the mat.";

text.match(/(?<=(t|T)he\s)(fat|mat)/g);	//["fat", "mat"]
text.match(/(t|T)he\s(fat|mat)/g);	//["The fat", "the mat"]

4.4 ?<!负后发断言

负后发断言 记作 (?<!...) 用于筛选所有匹配结果, 筛选条件为 其前不跟随着断言中定义的格式:

let text = "The fat cat sat on the mat.";

text.match(/(?<!(T|t)he\s)(cat)/g);	//["cat"]

5. 标志

标志也叫模式修正符, 因为它可以用来修改表达式的搜索结果. 这些标志可以任意的组合使用, 它也是整个正则表达式的一部分.

标志 描述
i 忽略大小写
g 全局搜索
m 多行的:锚点元字符^ $ 工作范围在每行的起始。

5.1 全局搜索

在上面的例子中已经看到很多关于全局标志符的使用,这里在对比一下使用和不使用全局标志符的差别:

let text = "The fat cat sat on the mat.";

text.match(/.(at)/);	//["fat", "at", index: 4, input: "The fat cat sat on the mat.", groups: undefined]
text.match(/.(at)/g);	//["fat", "cat", "sat", "mat"]

5.2 忽略大小写

忽略大小写的效果我们在上文也了解过,不过是另外一种复杂的写法,现在来使用 i 标志对比一下两种写法:

let text = "The fat cat sat on the mat.";

text.match(/(t|T)he/gi);	//["The", "the"]
text.match(/The/gi);	//["The", "the"]
text.match(/the/gi);	//["The", "the"]

5.3 多行修饰符

多行修饰符 m 常用于执行一个多行匹配.

像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾. 但我们如果想要它在每行的开头和结尾生效, 我们需要用到多行修饰符 m.

6. 贪婪匹配与惰性匹配

正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。

let text = "The fat cat sat on the mat.";

text.match(/(.*at)/);	//["The fat cat sat on the mat", "The fat cat sat on the mat", index: 0, input: "The fat cat sat on the mat.", groups: undefined]
text.match(/(.*?at)/);	//["The fat", "The fat", index: 0, input: "The fat cat sat on the mat.", groups: undefined]