正则表达式学习笔记

前言

正则表达式(英语:Regular Expression,常简写为 regex、regexp 或 RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

简单来说,正则表达式就是做了这么一个事情:制定一个规则,而后根据规则去文本中寻找符合规则的字符串。

它可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等。

在介绍正则表达式之前,首先推荐几个用于在线练习、测试正则表达式的网站:

基本匹配

首先,让我们从一个简单的例子入手,想象一个场景,我们有一段文章,想要找到其中所有的 hello,那么我们就可以使用正则表达式 hello 实现。

让我们来看看这个正则表达式 hello,它表示一个规则:由字母 h 开始,接着是 e, l, l, o

正则表达式: hello
匹配文本: Hello world! hello Mike, hello Emory!

可以看到,我们成功地找到了所有 hello,需要注意的是,正则表达式是大小写敏感的,因此我们无法匹配 Hello,通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,那么匹配的范围就扩大了。

元字符

上面展示了使用正则表达式的一个最简单的例子,有时候,我们可以直接制定需要寻找的字符串,就上面展示的那样;但是考虑另外一种情况,我们只记得要寻找的字符串的开头/结尾,但忘记了剩下的字符是什么;或者说如果我想要从一段文本中找到所有的手机号码、Email 地址、日期又改怎么办?

正则表达式中有许多被称为元字符的特殊代码,它们都具有一些特殊含义,下面展示了一些常见的元字符:

元字符 描述
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\d 匹配数字
\s 匹配任意的空白符
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

看到这些,可能会感到一头雾水,现在还是让我们通过例子来进行说明,现在我们有一个正则表达式 m.n,它表示的规则是,先匹配 m,然后匹配除换行符以外的任意字符,再匹配 n

正则表达式: m.n
匹配文本: mn, mon, min, max, moon

可以看到我们的正则表达式 m.n 匹配了 2 个字符 monmin

现在让我们再来看一个例子:

正则表达式: ^ab\d\w
匹配文本: **ab12**34
匹配文本: aab1234 (未匹配到)

正则表达式 ^ab\d\w 匹配以 a 开头,后面是一个 b,然后再匹配一个数字,最后匹配一个字符。

字符转义

如果你想查找元字符本身的话,那么就需要使用 \ 来取消这些字符的特殊意义,你应该使用 \. 来匹配 .,使用 \\ 来匹配 \

重复

很多情况下,我们不知道每个字符会出现多少次,正则表达式通过限定符来指定匹配子模式的次数。

代码 描述
* 重复 0 次或更多次
+ 重复 1 次或更多次
? 重复 0 次或 1 次
{n} 重复 n 次
{n,} 重复 n 次或更多次
{n,m} 重复 n 到 m 次

通过在正则表达式中加入限定符,能够让我们更灵活地匹配需要的字符。

下面是一个匹配日期格式的例子:

正则表达式: \d{4}-\d{1,2}-\d{1,2}
匹配文本: 1999-01-05
匹配文本: 2001-3-02 or 2005-11-25

上面的这个例子我相信也很容易理解,首先匹配 4 个数字,然后匹配 -,接着匹配 1 或 2 个数字以及 -,最后匹配 1 或 2 个数字。

字符类

虽然正则表达式提供了多种元字符供我们选择,但是如果我们想要匹配没有预定义元字符的字符串时该怎么办呢,比如我们想要匹配元音字母 aeiou,很显然并没有预先定义的元字符供我们使用。正则表达式提供了字符类很好地解决了这个问题。

我们用方括号来指定一个字符集,在方括号中使用连字符来指定字符集的范围,在方括号中的字符集不关心顺序。我们只需要在方括号里列出它们就行了,像 [aeiou] 就匹配任何一个英文元音字母。

我们也可以用 [] 来指定一个范围,就像 [0-9] 匹配一个数字,[a-zA-Z] 匹配一个字母。

下面是匹配 QQ 号的一个简单例子(QQ 号从 10000 开始),首先是 1-9 的一个数字,而后至少出现 4 个数字:

正则表达式: ^[1-9][0-9]{4,}
匹配文本: 126548732

分支条件

如果我们需要多个匹配规则时又要怎么办呢?比如,我既想要匹配 3 位数字,又想要匹配 4 位数字,这时单一的规则无法满足需求。

正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用 | 把不同的规则分隔开。

正则表达式: \d{4}|\d{3}
匹配文本: 1234, 567

可以看到使用 | 成功地满足了我们的需要,当然你可以将它理解为

值得注意的是,使用分枝条件时,要注意各个条件的顺序,如果我们交换前后顺序,即正则表达式: \d{3}|\d{4} 时,会得到不同的结果:

正则表达式: \d{4}|\d{3}
匹配文本: **123**4, 567

匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

反义

有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义

代码 描述
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了 x 以外的任意字符
[^aeiou] 匹配除了 aeiou 这几个字母以外的任意字符

正则表达式: [^c]ar
匹配文本: The car **par**ked in the **gar**age

分组

如果你想要重复多个字符,那么你可以使用小括号来指定子表达式,即分组

下面的例子是一个简单的 IP 地址匹配:

正则表达式: (\d{1,3}\.){3}\d{1,3}
匹配文本: 127.0.0.1, 192.168.0.1

我们还可以在 () 中用或字符 | 表示或:

正则表达式: (T|t)he|car
匹配文本: The car is parked in the garage

常用分组语法

分类 代码 描述
捕获 (exp) 匹配 exp,并捕获文本到自动命名的组里
捕获 (?<name>exp) 匹配 exp,并捕获文本到名称为 name 的组里,也可以写成(?'name’exp)
捕获 (?:exp) 匹配 exp,不捕获匹配的文本,也不给此分组分配组号
零宽断言 (?=exp) 匹配 exp 前面的位置
零宽断言 (?<=exp) 匹配 exp 后面的位置
零宽断言 (?!exp) 匹配后面跟的不是 exp 的位置
零宽断言 (?<!exp) 匹配前面不是 exp 的位置
注释 (?#comment) 提供注释

零宽断言

接下来的四个用于查找在某些内容(但并不包括这些内容)之前或之后的东西,也就是说它们像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。

同样举例说明:

正则表达式: \w+(?=ing)
匹配文本: I’m **sing**ing while you’re **danc**ing

可以看到,正则表达式成功匹配到了所有 ing 之前的部分,就像前面说的,用于查找在某些内容(但并不包括这些内容)之前或之后的东西。

类似的 ing(?!ing) 能够匹配所有 ing,并且它的后面不是 ing

正则表达式: ing(?!ing)
匹配文本: I’m singing while you’re dancing

贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。这被称为贪婪匹配。

例如,下面的这个例子中 a.*b 能够匹配最长的,以 a 开始,以 b 结束的字符串

正则表达式: a.*b
匹配文本: aabab

有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ?。这样 .*? 就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复

a.*?b 能够匹配最短的,以 a 开始,以 b 结束的字符串

正则表达式: a.*?b
匹配文本: aabab

标志

标志 描述
i 忽略大小写
g 全局搜索
m 多行模式
s 单行模式

总结

本文参考了网上一些已有的正则表达式学习教程,记录了一些自己学习过程,你可以 点击这里 查看有关正则表达式的更多用法信息。

参考资料