背景
最近主要做一些数据抽取/清洗相关的事情,所以正则表达式在字符串的处理上比较多,以前对正则这块的学习还是比较浅层,有些地方是没有学习全面的,所以借这次写文章的机会,把正则整体的梳理一遍。
文章脉络
- 正则表达式
- 匹配(一般用于查找)
- 普通元字符
- 特殊元字符
- 位置限定符
- 逻辑限定符
- 重复限定符
- 范围限定符
- 捕获(一般用于取值/替换)
- 分组
- 引用
- 匹配(一般用于查找)
第一部分:匹配(一般用于查找)
正则表达式由普通元字符和特殊元字符构成,普通元字符也就是我们日常的字母数字。特殊元字符则是具备特殊含义的字符,用于匹配一类字符。另外还有一类比较特殊的元字符叫做限定符,用于限定匹配位置或者特定区间的字符再或者是字符重复次数。
1.普通元字符
| 元字符 | 说明 |
|---|---|
| . | 匹配除换行符以外的任意字符 |
| \w | 匹配字母 数字 下划线 汉字 |
| \s | 匹配任意空白字符 |
| \d | 匹配数字 |
| \f | 匹配一个换页符 |
| \n | 匹配一个换行符 |
| \r | 匹配一个回车符 |
| pattern | 匹配pattern 字符,这里的pattern代表的就是普通的字符 |
| 这类字符主要用于匹配一类字符 |
- 如果匹配的字符跟正则的元字符冲突,那么需要转义处理加个\号就可以了(js只要加一个但是java需要2个,具体看语言实现的正则引擎处理方式)。
- 如果要进行反义操作,一般是把元字符改成大写。如:\s是任意空白字符,那么\S是非空白字符,\d匹配数字\D匹配非数字。当然也可以通过[^\s]或者[^\d]进行操作,这里的[]区间限定符和^逻辑限定符是接下来要说的。
2.特殊元字符
特殊元字符我这边按具体用途做了分类主要为:
- 位置限定符
- 逻辑限定符
- 重复限定符
- 范围限定符
2.1 位置限定符
| 限定符 | 说明 |
|---|---|
| ^ | 匹配行的首部 |
| $ | 匹配行的尾部 |
| \b | 匹配一个单词的边界,也就是指单词和空格间的位置 |
2.2 逻辑限定符
| 限定符 | 说明 |
|---|---|
| | | 逻辑或 |
| 逻辑非 |
一般逻辑限定符要跟范围限定符[]一起使用,如:
- [x|y]表示匹配x或者y字符
- [^\d]表示非数字
位置限定符^跟逻辑限定符^虽然符号相同,但是逻辑限定符^一般与范围限定符[]一起使用,位置限定符一般是在范围限定符号[]外使用。如:
- ^[x|y]表示匹配x或者y开头的字符
- ^[^\d]表示匹配非数字开头的字符
2.3 重复限定符
由于普通的元字符只适合描述单个字符,并没有次数的概念,所以一下的重复限定符用于描述匹配单词的次数。这里存在贪婪匹配模式和非贪婪匹配模式。
| 限定符 | 说明 |
|---|---|
| * | 重复0次或者多次 |
| + | 重复1次或者多次 |
| ? | 重复0次或1次 |
| {n} | 重复n次 |
| {n,m} | 重复n次到m次 |
| {n,} | 重复n次或者多次 |
| 举个例子如匹配两个数字(表达式为\d{2}),那么结果如下: |
接下来要讲重复限定符匹配中贪婪匹配和非贪婪匹配。如字面上的意思:
- 贪婪匹配:尽可能多的匹配
- 非贪婪匹配:尽可能少的匹配
默认情况下,所有的重复限定符都是贪婪匹配的。例子如下:
\d+会匹配1234567890所有的字符,匹配的结果就是["1234567890"],这个匹配模式是贪婪的。有的时候我们想尽可能少范围地匹配,那么就是以非贪婪的模式匹配。*非贪婪匹配只需要在重复限定符 ,+,?,{n},{n,},{n,m} 后面加多个?号,即匹配就是非贪婪模式。如\d+?匹配1234567890出来的结果就是1,总共匹配10次结果(尽可能少的匹配,那么能够匹配到的结果越多)。
2.4 范围限定符
范围限定符主要跟范围有关系,主要限定在某些字符或者某些词前或者词后。那么就需要用到范围限定符。
| 限定符 | 说明 | 例子 |
|---|---|---|
| - | 用于配合字母或者数字匹配a到z或者A到Z或者0到9区间字符 | a-z A-Z 0-9 |
| [pattern] | 匹配[]内的每一个字符 | [\d|a-z] 匹配小写字母或者数字 |
| 还有一种比较特殊的范围限定符称之为零宽断言(lookaround/zero-width assersion) | ||
| 限定符 | 说明 | 简单理解 |
| ----- | ---- | --- |
| (?=pattern) | 零宽正向先行断言 | 匹配 pattern 表达式的前面内容(左边),不返回本身 |
| (?!pattern) | 零宽负向先行断言 | 匹配非 pattern 表达式的前面内容(左边)内容,不返回本身。 |
| (?<=pattern) | 零宽正回顾后发断言 | 匹配 pattern 表达式的后面内容(右边)的内容,不返回本身。 |
| (?<!pattern) | 零宽负回顾后发断言 | 匹配非 pattern 表达式的后面内容(右边)内容,不返回本身。 |
为了便于理解这里我先简单列个文本:
爱我中华大地,美丽的中华儿女
零宽正向先行断言(?=pattern):中华(?=大地) 匹配"大地"前的“中华”
零宽负向先行断言(?!pattern):中华(?!大地) 匹配不是"大地"前面的“中华”(也就是儿女前的中华)
零宽正回顾后发断言(?<=pattern):(?<=爱我)中华 匹配"爱我"后面的“中华”
零宽负回顾后发断言(?<!pattern): (?<!爱我)中华 匹配不是"爱我"后面的“中华”
第二部分:捕获(一般用于取值/替换)
如果说第一部分讲的是用来查找查找匹配的文本,那么第二部分讲的就是怎么从匹配结果中捕获需要的内容,这里就不得提下分组以及分组完后怎么取引用分组的值。
1.分组
| 元字符 | 说明 |
|---|---|
| (pattern) | 匹配pattern并获取这一匹配,所获取的匹配将会放入匹配结果集合中. |
| (?:pattern) | 匹配pattern并获取这一匹配,所获取的匹配不会放入匹配结果集合中. |
| (?pattern) | 匹配pattern并获取这一匹配,所获取的匹配将会放入匹配结果集合中,并且命名该分组为name。 |
2.引用
引用是基于分组捕获结果而言的,如下两种表达式,匹配到的分组结果不同
| 表达式 | 引用值 |
|---|---|
| (pattern) | 匹配结果为数字编号组,正则表达式里引用方式:\k 或\number |
| (?pattern) | 匹配结果为命名编号组,正则表达式里引用方式:\k 或者\name |
(1)正则表达式内反向引用场景:
aabbbbgbddesddfiid
目的:查找字符串里成对的字母 正则表达式:(\w)\1
(2)替换引用分组值场景:
爱我中华大地,美丽的中华儿女
正则:(爱我中华)(大地)(,)(美丽的)(中华儿女) 替换:21符号为vscode的取匹配结果的方式)
替换前:
替换后:
总结
整体来说正则学习还是相对容易,主要日常过程中处理字符串这块应该经常用正则来处理,并且灵活结合这块各种元字符来满足自己的匹配结果,最后要了解怎么利用分组来进行对结果的内容进行抽取。另外本文的只考虑目前JavaScript使用的正则引擎,所以对正则表达式的支持也建立在这之上。不同正则引擎对正则的实现不同,所以不同语言使用的正则引擎不同也会影响最终的结果,但是对日常使用的部分应该是相同的。引擎以及语言的支持程度可以google查询。