正则表达式入门

178 阅读8分钟

一些推荐的正则表达式学习资源:

正则表达式30分钟入门教程

正则在线调试工具

Regex 101 - ZH-CN

书籍《正则表达式必知必会》

前言

正则表达式是处理文本匹配和替换的利器。有特定的语法和指令,学习正则表达式就像学习一门简单的语言,在掌握基础语法的同时也需要多练。

不同的正则表达式引擎对正则表达式的支持不同,比如说.net对于\w匹配符可以匹配汉字,而Javascript则不可。

1、从一个简单的例子理解正则表达式的执行逻辑

正则表达式最基本的运行逻辑是通过一个正则表达式文本,这段文本是一个整体,会被正则表达式引擎所解析,应用于所匹配或替换的文本中。 比如这段正则表达式: \bhello[14],包含三个部分,其中\b是用来匹配单词开头,hello就是匹配hello这个单词,[14]匹配数字1或4,这个正则整体的含义是匹配以hello单词作为开始,紧跟着数字1或4的这样一段文本。

image.png

2、字符匹配

字符匹配是正则表达式最基础也是最核心的功能。

2.1、纯文本匹配

纯文本匹配是最基本的用法。

image.png

2.2、字符分组

用元字符[]可以定义一个字符集合,这个字符集合里的任意元素都会参与文本匹配。 下面例子中,正则表达式[RE]表示匹配文本中所有字母R或E。

image.png

实际使用中,我们会频繁遇到字符区间,用元字符-可以定义一个字符区间, 比如说要匹配数字0-9,可以这样写[0-9]

image.png

以下是一些合法的字符区间:

  • A-Z,匹配从A到Z的所有大写字母。
  • a-z,匹配从a到z的所有小写字母。-
  • A-z,匹配从ASCII字符A到ASCII字符z的所有字母。这个模式一般不常用,因为它还包含着[和^等在ASCII字符表里排列在Z和a之间的字符。

取非匹配:元字符^可以对字符集合进行取非匹配。即匹配不在字符集合中的字符。

注意: ^的效果将作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟在^字符后面的那一个字符或字符区间。

image.png

2.3、常用元字符

元字符是一些在正则表达式中特殊含义的字符。元字符包括两种:一种是匹配文本的(如.);另一种是正则表达式语法所要求的,比如[]元字符转义:由于元字符代表了特殊含义,所以这些字符就无法代表他们本身,要匹配元字符需要用\进行转义。

image.png

2.3.1、空白字符匹配

空白字符不只包含空格,还有制表符(tab)回车符换行符等等。 匹配空白字符的元字符包括:

元字符说明
\f换页符
\n换行符
\r回车符
\t制表符
\v垂直制表
\s匹配所有空白字符

如下图,使用\s匹配所有空白字符

image.png

2.3.2、匹配特定字符类型

正则表达式中有一些元字符用来匹配某一类别的字符

元字符说明
\d匹配数字字符(等价于[0-9])
\D匹配非数字字符(等价于[^0-9])
\w匹配字母、数字、下划线字符(等价于[a-zA-Z0-9_])
\W匹配非字母数字下划线字符(等价于[^a-zA-Z0-9_])
\s匹配空白字符(等价于[\f\n\r\t\v])
\S匹配非空白字符(等价于[^\f\n\r\t\v])

💡在正则表达式中,通常同一个单词的大小写在功能上正好相反。

3、重复匹配

重复匹配是用来匹配连续出现的字符或字符集合。 正则表达式中用于重复次数匹配的模式如下:

代码/语法说明
*重复任意次(等价于 **{0,} **)
+重复1次或更多次(等价于 **{1,} **)
?重复0次或1次(等价于 **{0,1} **)
{n,m}重复n到m次,但尽可能少重复
{n,}重复n次以上,但尽可能少重复

来看一个简单的例子,正则表达式\b[a-z]{4,5}\b匹配长度为4或5的小写字母单词。 image.png

3.1、贪婪与懒惰

默认情况下,正则表达式都是贪婪模式,也就是匹配所有满足正则表达式模式的文本。但有时我们不需要匹配所有的,这时可以懒惰模式。 懒惰模式是最小可能匹配,只要一发现匹配,就返回结果,不要往下检查。

💡懒惰模式的匹配原理: 简单的来说,就是在匹配和不匹配都可以的情况下,优先不匹配,记录备选状态,并将匹配控制权交给正则表达式的下一个匹配字符。当后面的匹配失败时,回溯,进行匹配。

image.png

来看下面一个例子,左右两幅图,左边的正则使用懒惰模式,.*?只匹配<b></b>标签内的内容,匹配两个结果。而右边的正则使用贪婪模式,.*匹配了<b>标签后所有的结果。 image.png

4、位置匹配

某些情况下只需要对特定位置的文本进行匹配,这就需要用到位置匹配。 正则表达式中用于位置配置的元字符有两类:第一种是匹配单词边界,包括\b\B,第二种是^$用来匹配字符串的边界。

\b到底匹配的是什么呢?实际上\b匹配的是一个可以分割单词的位置,这个位置位于一个能够用来构成单词的字符和一个不能用来构成单词的字符之间(也就是\w\W之间)。 (注意\b不等于\W,不是一个具体的字符)

代码说明
\b匹配单词的开始或结束
\B不匹配单词的边界
^匹配字符串的开始
$匹配字符串的结束

来看一个简单的单词边界的例子,正则表达式\bhello\b,匹配一个单独的单词hello。这个表达式由三个部分组成,前后的\b匹配单词的边界,中间的hello就是文本匹配。

image.png

字符串边界的匹配:^$用来匹配字符串的边界,所谓字符串边界,就是指所匹配文本的边界。

image.png

💡启用分行匹配模式(? m)将使得正则表达式引擎把行分隔符也当做一个字符串分隔符来对待。

5、分组和引用

5.1、分组

所谓分组,也称作子表达式,就是将用括号**(****)**,将一个正则表达式划分为一系列的子表达式,使子表达式(分组)可以当作一个独立元素来使用。

就像数学中计算1+1的结果再乘以2,需要用(1+1)×2 ,将1+1用括号括起来代表独立的一部分。

我们来看一个使用分组进行年份匹配例子,这个例子中,左边的部分使用正则19|20\d{2}不能正确的匹配,是因为|操作符将整个正则表达式分为左右两部分,所以它实际匹配的是:19或20开头的四位数字。右边的部分将19|20用括号归为一个子表达式,从而正确的匹配。 image.png

5.2、引用

在正则表达式中,分组还有一个用途:**回溯引用。回溯引用可以理解为一个变量,可以引用正则表达式中的分组。回溯引用经常用于字符串的替换。回溯引用使用\1\2......的语法,\1代表正则表达式的第一个分组,\2代表第二个,依次类推。

来看一个HTML文本匹配的例子,我们需要匹配HTML中成对的标题标签,如(<H1>...</H1>),并忽略不成对的标签如<H1>...<H2>,这时就很自然的想到需要类似编程语言中变量的概念,来引用之前匹配的内容。

image.png

正则表达式<([hH][1-6])>.*?<\/\1>能够很好的解决这个问题,式中\1就是一个回溯引用,引用了第一个子表达式([hH][1-6])。

💡回溯引用引用的是的子表达式所匹配的结果。 这两个正则表达式不相等:([hH])\1 ([hH]) ([hH])

5.3、回溯引用用于文本替换

来看这样一个例子,我们需要替换文本中的邮箱地址(xx@163.com)为可HTML中可点击的标签地址。使用回溯引用可以很好的解决这个问题,使用正则表达式(\w+@\w+.com)来匹配邮箱地址,这样匹配到的文本就可以使用替换模式<a href="$1">

image.png

6、前后查找

有时我们还需要用正则表达式标记要匹配的文本的位置(而不仅仅是文本本身)。这就引出了前后查找(lookaround,对某一位置的前、后内容进行查找)的概念。

还是拿HTML标签匹配为例,假设我们需要提取的是<h1>标签之间的文字,而我们用如下的表达式匹配到的内容还包括标签本身,这时就需要用到前后查找

image.png

6.1、向前查找

向前查找是一个使用操作符(?=)的子表达式,需要匹配的文本跟在=后边。

有些书中向前查找也叫做**先行断言。**代表只匹配在操作符?=匹配的文本之前的文本。

来看一个匹配URL地址中协议名的例子:下图的两个正则表达式中,左边使用向前查找,只返回:之前的协议名,而右边返回了协议名+:image.png 我们来看看两者的差异: 左边的正则表达式.*(?=:),用向前查找(?=:)实现类似找到**:**之前的内容的作用。 实际上左表达式中的(?=:)与右边表达式中:代表的都是匹配到:,但(?=:)不把它包含到最终的匹配结果中。

💡 向前与向后指的是什么? 以表达式(?=:)为例,向前指的是在:的前边或后边。

💡 实际上对于表达式.*(?=:),正则表达式引擎会这样来执行:首先从左往右的匹配.*匹配的文本,也就是任意文本,然后判断是否在:的前边,在:前边的才会匹配。

6.2、向后查找

向后查找的操作符是(?<=)

6.3、前后查找的取非

操作符(?=)(?<=)代表的是正向前查找和正向后查找。还可以使用操作符!来对前后查找进行取非操作。 image.png 来看一个例子来理解,下边这张图,我们需要匹配不在$后的数字,可以用负向向后查找(<!\$),获取不在$后的数字。

image.png

7、正则速查表

7.1、 字符类

表达式描述
[abc]字符集。匹配集合中所含的任一字符。
[^abc]否定字符集。匹配任何不在集合中的字符。
[a-z]字符范围。匹配指定范围内的任意字符。
.匹配除换行符以外的任何单个字符。
\转义字符。
\w匹配任何字母数字,包括下划线(等价于[A-Za-z0-9_])。
\W匹配任何非字母数字(等价于[^A-Za-z0-9_])。
\d数字。匹配任何数字。
\D非数字。匹配任何非数字字符。
\s空白。匹配任何空白字符,包括空格、制表符等。
\S非空白。匹配任何非空白字符。

7.2、 分组和引用

表达式描述
(expression)分组。匹配括号里的整个表达式。
(?:expression)非捕获分组。匹配括号里的整个字符串但不获取匹配结果,拿不到分组引用。
\num对前面所匹配分组的引用。

7.3、位置匹配

表达式描述
^匹配字符串或行开头。
$匹配字符串或行结尾。
\b匹配单词边界。
\B匹配非单词边界。

7.4、重复次数匹配

表达式描述
?匹配前面的表达式0个或1个。即表示可选项。
+匹配前面的表达式至少1个。
*匹配前面的表达式0个或多个。
|或运算符。并集,可以匹配符号前后的表达式。
{m}匹配前面的表达式m个。
{m,}匹配前面的表达式最少m个。
{m,n}匹配前面的表达式最少m个,最多n个。

7.5、 前后查找

表达式描述
(?=)正向预查。
(?!)正向否定预查。
(?<=)反向预查。
(?<!)反向否定预查。

7.6、特殊标志

表达式描述
/.../i忽略大小写。
/.../g全局匹配。
/.../m多行修饰符。用于多行匹配。