正则表达式

Resources:

  1. The Missing semester 第四讲:https://missing-semester-cn.github.io/2020/data-wrangling/

  2. https://www.bilibili.com/video/BV14u411s7c6/?spm_id_from=333.999.0.0&vd_source=95431b49fa3870ec7be36eb042a1bb35

  3. https://www.runoob.com/w3cnote/regular-expression-30-minutes-tutorial.html

  4. https://regexone.com/

简介:

正则表达式是一种文本模式,包括普通字符(例如,a到z之间的字母)和特殊字符(称为”元字符“),可以用来描述和匹配字符串的特定模式。正则表达式是一种用于模式匹配和搜索文本的工具,提供了一种灵活且强大的方式来查找、替换、验证和提取文本数据,并可以应用于各种编程语言和文本处理工具中,如JavaScript、Python、Java、Perl等。

实例:

正则表达式可以用某种格式来匹配所需要的字符串,在一般的匹配模式中,分为普通字符和元字符。普通字符将会根据字面意义来执行匹配,如在正则表达式中单独写入的a将匹配一般字符串中的a。而对于元字符,则具有特殊的含义,部分元字符的含义与应用如下所示:

1
2
3
4
\d 匹配任意Digit的字符,也即[0-9]的匹配
\D 匹配任意非Digit的字符
. 一类通配符,用来匹配任意的字符
\. 是英文句号的转义形式,对应字符"."。同理,一般的*、?、+都需要用\来进行转义,从而表示对应的字符。

字符框:

1
2
3
[ ] : 匹配括号内的任意一个字符,可以用-来表示一定ascii值范围所对应的字符,如[a-z]对应所有的小写字符,同理,下面取反也可以作用于某一范围指定的字符
[^ ] : 匹配除了括号内的字符外的任意一个字符,例如[^abc]可以匹配除了abc以外的任意字符

多个字符索要支持的范围,只需要多写一些到括号内即可。

如:

1
2
[a-zA-Z0-9] 即可匹配所有小写、大写字母以及0-9的数字
[^0-9_] 即可匹配所有非0-9数字或下划线的字符

字符边界:

1
2
3
4
^ : 匹配字符串的开头
$ : 匹配字符串的结尾
\b: 匹配单词边界
\B: 匹配非单词边界

匹配量词:

为了满足匹配各类字符串的需要,对于匹配的模式,正则表达式会通过一系列量词来完成约束。如:

1
*	代表匹配前面的模式0次或多次

假设我们要匹配一个任意长度的仅有小写字母包括的字符串,我们可以这样写:

1
patt= ^[a-z]*$

上面的模式中,字符^和$是锚定符,分别对应正则表达式匹配模式的起点和终点。注意,量词*一般放在对应限制字符的后面,如上述匹配语句中, *在[a-z]后,代表按照0个或无数个小写字符的字符串执行匹配。

1
+	代表匹配前面的模式1次或多次

+和*是类似的,只不过+表示至少匹配一次对应的模式,也即小写字符的字符串长度至少为1。

1
?	代表匹配前面的模式0次或1次

?和之前的匹配量词*和+的区别在于,后两个量词的匹配是“贪婪的”,也即会最大化匹配的次数,将字符串中所有符合匹配条件的内容全部找出来,而?仅仅会这样做一遍,只会匹配第一个找到的符合模式的字符串结构。

1
2
3
{n}	匹配前面的模式恰好n次(=n)
{n,} 匹配前面的模式至少n次(>= n)
{n,m} 匹配前面的模式至少n次且不超过m次(x >= n && x <= m)

以下是一些匹配模式重复的例子:

1
2
a{3} 连续匹配三个重复的a
a{1.3} 匹配1-3个重复的a

这里,有一个比较奇怪的例子,例如RegexOne的第一个Lesson里,给出:

1
2
3
abcdef
abcd
abc

我们使用如下的模式:

1
^[abc]+[a-z]{1,3}$

可以完成所有的匹配,也即上述正则表达式可以匹配上字符串”abc”,这说明,正则表达式中+的匹配逻辑并不完全是贪心的。若是贪心的,则abc字符串会先与[abc]+这部分匹配上,后面没有字符的话,就无法与后方[a-z]{1,3}的约束相匹配了,这说明正则表达式的匹配是全局最优的。在尽可能保证能得到匹配的情况下,实现贪心的匹配。

捕获组:

正则表达式允许我们不光匹配文本,而且从文本中提取对应信息,用以进一步的处理。这一步是通过定义字符组并使用特殊的元字符括号()来实现的。任何一对括号内维护的子模式都会被作为组来捕获,在实际应用中,这种方式可以用来从各种数据中提取手机号码,以及电子邮箱信息。

假设我们正在通过一种命令行工具,列出云端服务器中所拥有的照片文件,如我们可以使用

1
^(IMG\d+\.png)$

来捕获图片文件,并且提取全部的文件名。但是如果你只想要捕获文件名,而忽略后缀扩展名,那么我们只需要利用如下的捕获组:

1
^(IMG\d+)\.png$

即可捕获在period英文句号之前的图像文件名。

1
2
( ): 用于分组和捕获子表达式
(?: ):用于分组但不捕获子表达式

对于复杂的数据,我们可能会进行信息的多层提取,这种诉求可以被嵌套分组捕获来实现。具体的,捕获组的结果是按照他们被定义的顺序来呈现的。

接上例,假设我们从一个列表中捕获所有的图像文件的文件名,若这些照片的名称都有某个数字编号,如IMG197.png,则在捕获名称的同时,我们可以用嵌套的方式来获得照片的编号。

1
^(IMG(\d+))\.png$

注意,嵌套分组是从左往右被顺序解析的,而第一个捕获组的内容,就对应着第一对括号内包含的内容。

除了捕获之外,括号还可以用来进行分组,内容就会按照分组的方式来进行匹配,如:

1
^I\slove\s(cats|dogs)$

将会匹配文本:I love cats 或I love dogs,这里括号内使用了|或逻辑来链接两种不同的模式。

返回引用:

在捕获组中收获的格式内容在许多系统中可以用\0, \1的方式来进行引用。如\0通常表示整个匹配的文本内容,\1则表示第一个捕获组所捕获的内容,\2…以此类推即可。

RegexOne练习内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
L1: ^abc[a-z]*$
L1/2: ^.*123.*$
L2: ^[a-z0-9?=+]{1,3}\.$
L3: ^[cmf]an$
L4: ^[^b]og$
L5: ^[A-Z][^abc][a-c]$
L6: ^waz{2,5}up$
L7: ^a[a-c]+$
L8: ^[0-9]*\s[a-z]*\sfound\?$
L9: ^[1-3]\.\s{1,}abc$
L10: ^Mission: successful$
L11: ^([a-z_0-9]+)\.pdf$
L12: ^([A-Z][a-z]+\s(\d{4}))$
L13: ^([0-9]*)x([0-9]*)$
L14: ^I\slove\s(cats|dogs)$
L15: ^[0-9a-zA-Z\.\!\@\*\$\&\#\%\s]*$ 复杂版,实际上.*即可

P1: ^[0-9e,\.\-]*$
P2: ^\(?1?\s?([0-9]{3}).*$
P3: ^([a-z\.]*)\+?\@?.*$
P4: ^<([a-z]+).*$\
P5: ^([a-z0-9_]*)\.(jpg|png|gif)$
P6: ^\s*([A-Za-z]+[a-z\.\s]*)$
P7: ^.*at\swidget\.List\.([a-zA-Z]+)\((ListView\.java)\:(\d+)\)$
P8: ^([a-z]*)://([\w\.\-]+)(:(\d+))?.*$

注意,在需要控制捕获组的个数的时候,可以用?在捕获组后加上修饰,这样在没有该捕获组内容的时候,该捕获组就不会出现在返回引用的内容中。

https://regexone.com/problem/extracting_url_data?