峰哥带你玩系列 之 正则表达式

0. 导言

你是否在文本编辑器里查找和替换时只会使用简单的通配符?

你是否在编写程序验证用户输入合法性时繁琐地写了一堆方法?

你是否在查找不固定但有规律的内容时挨个输入不同的文本到查找框?

你是否想要使用简单操作即可批量完成复杂的识别、验证和替换?

那么, 你要找的是不是: 正则表达式 ?

0.1. 什么是正则表达式

正则表达式, 又称 规则表达式, (Regular Expression, 在代码中常简写为 regex regexpRE), 是一种文本模式, 包括 普通字符 (例如 a 到 z 之间的字母) 和 特殊字符 (称为 *”元字符”*), 是计算机科学的一个概念. 正则表达式使用单个字符串来描述和匹配一系列匹配某个句法规则的字符串, 通常被用来检索和替换那些符合某个 模式 (pattern) 的文本.

在最近的六十年中, 正则表达式逐渐从模糊而深奥的数学概念, 发展成为在计算机各类工具和软件包应用中的主要功能. 不仅仅众多 UNIX 工具支持正则表达式, 近二十年来, 在 Windows 的阵营下, 正则表达式的思想和应用在大部分 Windows 开发者工具包中得到支持和嵌入应用. 从正则表达式在 Microsoft Visual Basic 6 或 Microsoft VBScript 到 .NET Framework 中的探索和发展, Windows 系列产品对正则表达式的支持发展到无与伦比的高度, 几乎所有 Microsoft 开发者和所有 .NET 语言都可以使用正则表达式. 如果你是一位接触计算机语言的工作者, 那么你会在主流操作系统 (Linux、UNIX、Windows、HP、BeOS 等), 主流的开发语言 (delphi、Scala、PHP、C#、Java、C++、Objective-C、Swift、VB、Javascript、Ruby 以及 Python 等), 还有数以亿万计的各种应用软件中, 都可以看到正则表达式优美的舞姿.

0.2. 正则表达式的使用场景

给定一个正则表达式和另一个字符串, 我们可以达到如下的目的:

  • 判断给定的字符串是否符合正则表达式的过滤逻辑 (称作 匹配);
  • 可以通过正则表达式, 从字符串中获取我们想要的特定部分.

0.3. 正则表达式的特点

正则表达式的特点是:

  • 灵活性、逻辑性和功能性强;
  • 可以迅速地用极简单的方式达到字符串的复杂控制;
  • 对于刚接触的人来说, 比较晦涩难懂.

由于正则表达式主要应用对象是文本, 因此它在各种文本编辑器场合都有应用. 小到著名编辑器 EditPlusVisual Studio Code, 大到 Microsoft WordVisual Studio 等大型编辑器, 都可以使用正则表达式来处理文本内容.

1. 基础: 正则表达式的语法

1.1. 正则表达式的模式

1.1.1. 普通字符

普通字符包括没有显式指定为元字符的所有可打印和不可打印字符. 这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号.

字符描述
[ABC]匹配 [...] 中的所有字符.
例如 [aeiou] 匹配字符串 “google runoob taobao” 中所有的 e o u a 字母.
[^ABC]匹配除了 [...] 中字符的所有字符.
例如 [^aeiou] 匹配字符串 “google runoob taobao” 中除了 e o u a 字母的所有字符.
[A-Z][A-Z] 表示一个区间, 匹配所有大写字母;
类似地, [a-z] 表示所有小写字母, [0-9] 表示所有数字.
扩展: 
.
匹配除换行符 (\n\r) 之外的任何单个字符, 相等于 [^\n\r].
扩展: 
[\s\S]
匹配所有.
其中, \s 是匹配所有空白符, 包括换行; \S 非空白符, 不包括换行. 下文将详述.
扩展: 
\w
匹配字母、数字、下划线. 等价于 [A-Za-z0-9_].

请使用 正则表达式在线测试 实际尝试一下.

1.1.2. 非打印字符

非打印字符也可以是正则表达式的组成部分. 下表列出了表示非打印字符的转义序列:

字符描述
\cx匹配由 x 指明的控制字符.
例如, \cM 匹配一个 Control-M回车符.
x 的值必须为 A-Za-z 之一, 否则将 c 视为一个原义的 'c' 字符.
\f匹配一个换页符. 等价于 \x0c 或 \cL.
\n匹配一个换行符. 等价于 \x0a\cJ.
\r匹配一个回车符. 等价于 \x0d\cM.
\s匹配任何空白字符, 包括 空格制表符换页符 等等.
等价于 [ \f\n\r\t\v]. 注意方括号里第一个字符是空格.
而 Unicode 正则表达式会匹配全角空格符.
\S匹配任何非空白字符. 等价于 [^ \f\n\r\t\v]. 注意方括号中的空格.
\t匹配一个制表符. 等价于 \x09\cI.
\v匹配一个垂直制表符. 等价于 \x0b\cK.

1.1.3. 特殊字符

所谓特殊字符, 就是一些有特殊含义的字符, 如 ni*no 中的 *, 是具有特别含义的.

如果要查找字符串中的 * 符号, 则需要对 * 进行 转义, 即在其前加一个 \, 例如 ni\*no 匹配字符串 NNNni*noOOO.

许多元字符要求在试图匹配它们时特别对待. 若要匹配这些特殊字符, 必须首先使字符 “转义“, 即将反斜杠字符 \ 放在它们前面.

下表列出了正则表达式中的特殊字符:

特殊字符描述
$匹配输入字符串的结尾位置.
如果设置了 RegExp 对象的 Multiline 属性, 则 $ 也匹配 \n\r.
要匹配 $ 字符本身, 请使用 \$.
( )标记一个子表达式的开始和结束位置.
子表达式可以获取供以后使用.
要匹配这些字符, 请使用 \(\).
*限定符, 匹配前面的子表达式零次或多次. 要匹配 * 字符, 请使用 \*.
+限定符, 匹配前面的子表达式一次或多次. 要匹配 + 字符, 请使用 \+.
.匹配除换行符 \n 之外的任何单字符. 要匹配 ., 请使用 \..
[标记一个中括号表达式的开始. 要匹配 [, 请使用 \[.
?限定符, 匹配前面的子表达式零次或一次, 或指明一个非贪婪限定符. 要匹配 ? 字符, 请使用 \?
非贪婪限定符: 在 * 紧随其它限定符 (*, +, ?, {n}, {n,}, {n,m}) 之后时, 匹配模式变成了非贪婪模式, 否则通常的行为是贪婪模式. 例如, 在字符串 fooood 中, fo+? 只匹配 fo 部分, 而 fo+ 则匹配 foooo 部分.
\将下一个字符标记为或特殊字符, 或原义字符, 或向后引用, 或八进制转义符.
例如:  n 匹配字符 n, \n 匹配换行符, 序列 \\ 匹配 \,而 \( 则匹配 (.
向后引用的例子
^匹配输入字符串的开始位置, 除非在方括号表达式中使用.
当该符号在方括号表达式中使用时, 表示不接受该方括号表达式中的字符集合. 要匹配 ^ 字符本身, 请使用 \^.
{标记限定符表达式的开始. 要匹配 {, 请使用 \{.
|指明两项之间的一个选择, 即 “或” 操作. 要匹配 |,请使用 `\

1.1.4. 限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配.

正则表达式的限定符有:

字符描述
*匹配前面的子表达式零次或多次.
例如, zo* 能匹配 z 以及 zoo.
* 等价于 {0,}.
+匹配前面的子表达式一次或多次.
例如, zo+ 能匹配 zo 以及 zoo,但不能匹配 z.
+ 等价于 {1,}.
?匹配前面的子表达式零次或一次.
例如, do(es)? 可以匹配 dodoesdoxy 中的 dodoes.
? 等价于 {0,1}.
{n}n 是一个非负整数. 匹配确定的 n 次.
例如, o{2} 不能匹配 Bob 中的 o, 但是能匹配 food 中的 oo.
{n,}n 是一个非负整数. 至少匹配 n 次.
例如, o{2,} 不能匹配 Bob 中的 o, 但能匹配 foooood 中的 所有o.
o{1,} 等价于 o+. o{0,} 则等价于 o*.
{n,m}m 和 n 均为非负整数, 其中 n <= m. 最少匹配 n 次且最多匹配 m 次.
例如, o{1,3} 将匹配 fooooood 中的 前三个o. o{0,1} 等价于 o?.
请注意, 在逗号和两个数之间 不能有空格.

例1:

以下正则表达式匹配一个正整数.

[1-9] 设置第一个数字不是 0, [0-9]* 表示任意多个数字:

[1-9][0-9]*

请注意, 限定符出现在范围表达式之后. 因此, 它应用于整个范围表达式, 在本例中, 只指定从 0 到 9 的数字 (包括 0 和 9).

这里不使用 + 限定符, 因为在第二个位置或后面的位置不一定需要有一个数字. 也不使用 ? 字符, 因为使用 ? 会将整数限制到只有两位数.

例2:

如果你想设置 0~99 的两位数, 可以使用下面的表达式来至少指定一位但至多两位数字.

[0-9]{1,2}

上面的表达式的缺点是, 只能匹配两位数字, 而且可以匹配 0, 00, 01~99.

让我们改进一下, 匹配 0~99 的整数表达式如下:

0|([1-9][0-9]?)

注意: * 和 + 等所有限定符都是 贪婪 的, 它们会尽可能多地匹配文字, 在它们的后面加上一个 ? 就可以实现 非贪婪最小匹配.

例3:

让我们来搜索 HTML 文档, 以查找在 h1 标签中的内容. HTML 文档如下:

<h1>Nino is sagacious.</h1>

如果我们使用如下表达式

<.*>

则会 贪婪 地匹配整行, 即从开始的 < 到结束的 >.

如果只需要匹配 <h1>, 则需要写成下面的表达式

<.*?>

* 后紧接 ? 即可使其变成 非贪婪 模式, 匹配至第一个 >.

思考: 为什么下面的表达式也可以针对上述例子达到同样的非贪婪效果?

<\w+>

如果文本变为 Buuuuug, 期望匹配的结果为 Bu, 使用 \w+ 还可行么?

1.1.5. 定位符

定位符可将正则表达式固定到行首或行尾.

它们还可以创建这样的正则表达式, 这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾.

定位符用来描述字符串或单词的边界, ^$ 分别指字符串的开始与结束, \b 描述单词的前或后边界, \B 表示非单词边界.

正则表达式的定位符有:

字符描述
^匹配输入字符串开始的位置.
如果设置了 RegExp 对象的 Multiline 属性, ^ 还会与 \n\r 之后的位置匹配.
$匹配输入字符串结尾的位置.
如果设置了 RegExp 对象的 Multiline 属性, $ 还会与 \n\r 之前的位置匹配.
\b匹配一个单词边界, 即字与空格间的位置.
\B非单词边界匹配.

注意:不能将限定符与定位符一起使用. 由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置, 因此不允许诸如 ^* 之类的表达式.

若要匹配一行文本开始处的文本, 请在正则表达式的开始使用 ^ 字符. 不要将 ^ 的这种用法与中括号表达式内的用法混淆. 请自行翻到 普通字符 一节中复习 ^ 在中括号内的表达式用法.

若要匹配一行文本的结束处的文本, 请在正则表达式的结束处使用 $ 字符.

例4:

例2 中, 我们最后得到的 0~99 之间的整数表达式为

0|([1-9][0-9]?)

观察这个表达式, 它也可以匹配 0013 中的部分内容: 0 0 13.

如果我们将这个表达式用于检测用户输入是否合法, 则无法满足检测要求, 因为它可以匹配到非边界内的字符.

让我们再改进一下, 将输入完全限制在0~99之间的整数, 不能包含其它内容:

^(0|([1-9][0-9]?))$

注意此时在整个表达式的前后都加入了定位符, 并将原式使用圆括号括起来.

括起来这个操作很重要, 有时可以避免匹配混乱. 请在 正则表达式在线测试 中测试首尾中间整体不加括号的效果.


例5:

若要在搜索章节标题时使用定位点, 下面的正则表达式匹配一个章节标题, 该标题只包含两个尾随数字, 并且出现在行首:

^Chapter [1-9][0-9]{0,1}

真正的章节标题不仅出现行的开始处, 而且它还是该行中仅有的文本. 它既出现在行首又出现在同一行的结尾. 下面的表达式能确保指定的匹配只匹配章节而不匹配交叉引用. 通过创建只匹配一行文本的开始和结尾的正则表达式, 就可做到这一点.

^Chapter [1-9][0-9]{0,1}$

匹配单词边界稍有不同, 但向正则表达式添加了很重要的能力. 单词边界是单词和空格之间的位置. 非单词边界是任何其他位置.

例6:

下面的表达式匹配单词 Chapter 的开头三个字符, 因为这三个字符出现在单词边界后面:

\bCha

\b 字符的位置是非常重要的. 如果它位于要匹配的字符串的开始, 它在单词的开始处查找匹配项. 如果它位于字符串的结尾, 它在单词的结尾处查找匹配项.

例如, 下面的表达式匹配单词 Chapter 中的字符串 ter, 因为它出现在单词边界的前面:

ter\b

下面的表达式匹配 Chapter 中的字符串 apt, 但不匹配 aptitude 中的字符串 apt:

\Bapt

字符串 apt 出现在单词 Chapter 中的非单词边界处, 但出现在单词 aptitude 中的单词边界处. 对于 \B 非单词边界运算符, 不可以匹配单词的开头或结尾.

如果是下面的表达式, 就不匹配 Chapter 中的 Cha:

\BCha

1.1.6. 选择与分组

用圆括号 () 将所有选择项括起来, 相邻的选择项之间用 | 分隔.

() 表示捕获分组, () 会把每个分组里的匹配的值保存起来, 多个匹配值可以通过数字 n 来查看 (n 是一个数字, 表示第 n 个捕获组的内容).

但用圆括号会有一个副作用, 使相关的匹配会被缓存, 此时可用 ?: 放在第一个选项前来消除这种副作用.

?: 在这里称为 非捕获元.

还有另外两个非捕获元是 ?=?!, 这两个还有更多的含义. 参见 正则表达式的先行断言(lookahead)和后行断言(lookbehind) 作为了解即可.

1.1.7. 反向引用

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中, 所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储.

缓冲区编号从 1 开始, 最多可存储 99 个捕获的子表达式. 每个缓冲区都可以使用 \n 访问, 其中 n 为一个标识特定缓冲区的一位或两位十进制数.

可以使用非捕获元字符 ?:?=?! 来重写捕获, 忽略对相关匹配的保存.

参看 向后引用的例子 以了解如何使用反向引用.

例7:

使用 Javascript 代码, 利用正则表达式分割一段网址, 并输出所有数据:

var str = "https://nino.itbears.club:443/technology/2020-06/actual-combat-thinkphp-vue-01/";
var patt1 = /(\w+):\/\/([^/:]+)(:\d*)?([^# ]*)/;
arr = str.match(patt1);
for (var i = 0; i < arr.length ; i++) {
    document.write(arr[i]);
    document.write("<br>");
}

点击 此处 并将上述源码替换到 <script></script> 中间, 然后 点击运行, 观察结果.

第三行代码 str.match(patt1) 返回一个数组, 实例中的数组包含 5 个元素, 索引 0 对应的是整个字符串, 索引 1 对应第一个匹配符 (括号内), 以此类推.

第一个括号子表达式捕获 Web 地址的协议部分. 该子表达式匹配在冒号和两个正斜杠前面的任何单词.

第二个括号子表达式捕获地址的域地址部分. 子表达式匹配非 :/ 之后的一个或多个字符.

第三个括号子表达式捕获端口号 (如果指定了的话) . 该子表达式匹配冒号后面的零个或多个数字. 只能重复一次该子表达式.

最后, 第四个括号子表达式捕获 Web 地址指定的路径和 / 或页面信息. 该子表达式能匹配不包括 #空格字符 的任何字符序列.

将正则表达式应用到上面的 URI, 各子匹配项包含下面的内容:

第一个括号子表达式包含: https 第二个括号子表达式包含: nino.itbears.club 第三个括号子表达式包含: :443 第四个括号子表达式包含: /technology/2020-06/actual-combat-thinkphp-vue-01/

看到这里, 细心的你可能发现了, 第三个括号子表达式中包含了 :443, 其中的冒号不是我们想要的结果.

改进此表达式, 利用非捕获元字符 ?: 进行重写捕获, 忽略前导冒号:

(\w+):\/\/([^/:]+)((?::)\d*)?([^# ]*)

然而结果出人意料, 依然是 :443.

仔细分析, 发现此时出现了 捕获嵌套. 冒号包含的捕获忽略了, 但外层的捕获依然没有被忽略.

我们重新改进此表达式:

(\w+):\/\/([^/:]+)(?:(?::)(\d*))?([^# ]*)

将外层的捕获忽略, 同时给端口号的 \d* 加上了括号用于捕获. 此时端口号显示为 443, 结果正确了.

1.2. 正则表达式的修饰符 (标记)

我们以 Javascript 为例, 讲解正则表达式的修饰符.

修饰符也称为标记, 用于指定额外的匹配策略. 标记不写在正则表达式里, 标记位于表达式之外, 格式如下

/pattern/flags

模式 (即正则表达式) 用两个正斜杠 / 包围, 后紧跟标记.常用标记如下:

修饰符含义描述
iignore – 不区分大小写将匹配设置为不区分大小写, 搜索时不区分大小写: A 和 a 没有区别.
gglobal – 全局匹配查找所有的匹配项.
mmulti line – 多行匹配使边界字符 ^$ 匹配 每一行 的开头和结尾, 记住是多行, 而不是整个字符串的开头和结尾.
s特殊字符圆点 . 中包含换行符 \n默认情况下的圆点 . 是匹配除换行符 \n 之外的任何字符, 加上 s 修饰符之后, . 中包含换行符 \n.

具体实践可在 正则表达式在线测试 中看到正则表达式输入框后的 修饰符 选择.

2. 总结: 正则表达式中的元字符

下表总结了元字符的完整列表以及它们在正则表达式上下文中的行为:

字符描述
\将下一个字符标记为或特殊字符, 或原义字符, 或向后引用, 或八进制转义符.
例如:  n 匹配字符 n, \n 匹配换行符, 序列 \\ 匹配 \,而 \( 则匹配 (.
向后引用的例子
^匹配输入字符串的开始位置, 除非在方括号表达式中使用.
当该符号在方括号表达式中使用时, 表示不接受该方括号表达式中的字符集合. 要匹配 ^ 字符本身, 请使用 \^.
$匹配输入字符串结尾的位置.
如果设置了 RegExp 对象的 Multiline 属性, $ 还会与 \n\r 之前的位置匹配.
*匹配前面的子表达式零次或多次.
例如, zo* 能匹配 z 以及 zoo.
* 等价于 {0,}.
+匹配前面的子表达式一次或多次.
例如, zo+ 能匹配 zo 以及 zoo,但不能匹配 z.
+ 等价于 {1,}.
?匹配前面的子表达式零次或一次.
例如, do(es)? 可以匹配 dodoesdoxy 中的 dodoes.
? 等价于 {0,1}.
{n}n 是一个非负整数. 匹配确定的 n 次.
例如, o{2} 不能匹配 Bob 中的 o, 但是能匹配 food 中的 oo.
{n,}n 是一个非负整数. 至少匹配 n 次.
例如, o{2,} 不能匹配 Bob 中的 o, 但能匹配 foooood 中的 所有o.
o{1,} 等价于 o+. o{0,} 则等价于 o*.
{n,m}m 和 n 均为非负整数, 其中 n <= m. 最少匹配 n 次且最多匹配 m 次.
例如, o{1,3} 将匹配 fooooood 中的 前三个o. o{0,1} 等价于 o?.
请注意, 在逗号和两个数之间 不能有空格.
?限定符, 匹配前面的子表达式零次或一次, 或指明一个非贪婪限定符. 要匹配 ? 字符, 请使用 \?
非贪婪限定符: 在 * 紧随其它限定符 (*, +, ?, {n}, {n,}, {n,m}) 之后时, 匹配模式变成了非贪婪模式, 否则通常的行为是贪婪模式. 例如, 在字符串 fooood 中, fo+? 只匹配 fo 部分, 而 fo+ 则匹配 foooo 部分.
.匹配除换行符 \n 之外的任何单字符. 要匹配 ., 请使用 \..
要匹配包括 '\n' 在内的任何字符, 请使用像 (.|\n) 的模式.
(pattern)匹配 pattern 并获取这一匹配. 所获取的匹配可以从产生的 Matches 集合得到, 在 VBScript 中使用 SubMatches 集合, 在 JScript 中则使用 $0...$9 属性. 要匹配圆括号字符, 请使用 \(\).
(?:pattern)匹配 pattern 但不获取匹配结果, 也就是说这是一个非获取匹配, 不进行存储供以后使用.
这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用.
例如,  industr(?:y|ies) 就是一个比 industry|industries 更简略的表达式.
(?=pattern)正向肯定预查 (look ahead positive assert), 在任何匹配 pattern 的字符串开始处匹配查找字符串.
这是一个非获取匹配, 也就是说, 该匹配不需要获取供以后使用.
例如,Windows(?=95|98|NT|2000)能匹配 "Windows2000 中的 Windows, 但不能匹配 Windows3.1 中的 Windows.
预查不消耗字符, 也就是说, 在一个匹配发生后, 在最后一次匹配之后立即开始下一次匹配的搜索, 而不是从包含预查的字符之后开始.
(?!pattern)正向否定预查 (negative assert), 在任何不匹配 pattern 的字符串开始处匹配查找字符串. 
这是一个非获取匹配, 也就是说, 该匹配不需要获取供以后使用.
例如,Windows(?!95|98|NT|2000)能匹配 "Windows3.1 中的 Windows, 但不能匹配 Windows2000 中的 Windows.
预查不消耗字符, 也就是说, 在一个匹配发生后, 在最后一次匹配之后立即开始下一次匹配的搜索, 而不是从包含预查的字符之后开始.
(?<=pattern)反向 (look behind) 肯定预查, 与正向肯定预查类似, 只是方向相反.
例如, (?<=95|98|NT|2000)Windows 能匹配 2000Windows 中的 Windows, 但不能匹配 3.1Windows 中的 Windows.
(?<!pattern)反向否定预查, 与正向否定预查类似, 只是方向相反.
例如(?<!95|98|NT|2000)Windows 能匹配 3.1Windows 中的 Windows ,但不能匹配 2000Windows 中的 Windows.
x|y匹配 xy.
例如, z|food 能匹配 z 或 food. (z|f)ood 则匹配 zood 或 food.
[xyz]字符集合. 匹配所包含的任意一个字符.
例如,  [abc] 可以匹配 plain 中的 a.
[^xyz]负值字符集合. 匹配未包含的任意字符.
例如,  [^abc] 可以匹配 plain 中的 plin.
[a-z]字符范围. 匹配指定范围内的任意字符.
例如,[a-z] 可以匹配 az 范围内的任意小写字母字符.
[^a-z]负值字符范围. 匹配任何不在指定范围内的任意字符.
例如, [^a-z] 可以匹配任何不在 az 范围内的任意字符.
\b匹配一个单词边界, 也就是指单词和空格间的位置.
例如, er\b 可以匹配 never 中的 er, 但不能匹配 verb 中的 er.
\B匹配非单词边界.
例如, er\B 能匹配 verb 中的 er, 但不能匹配 never 中的 er.
\cx匹配由 x 指明的控制字符.
例如, \cM 匹配一个 Control-M回车符.
x 的值必须为 A-Za-z 之一, 否则将 c 视为一个原义的 'c' 字符.
\d匹配一个数字字符. 等价于 [0-9].
\D匹配一个非数字字符. 等价于 [^0-9].
\f匹配一个换页符. 等价于 \x0c 或 \cL.
\n匹配一个换行符. 等价于 \x0a\cJ.
\r匹配一个回车符. 等价于 \x0d\cM.
\s匹配任何空白字符, 包括 空格制表符换页符 等等.
等价于 [ \f\n\r\t\v]. 注意方括号里第一个字符是空格.
而 Unicode 正则表达式会匹配全角空格符.
\S匹配任何非空白字符. 等价于 [^ \f\n\r\t\v]. 注意方括号中的空格.
\t匹配一个制表符. 等价于 \x09\cI.
\v匹配一个垂直制表符. 等价于 \x0b\cK.
\w匹配字母、数字、下划线. 等价于 [A-Za-z0-9_].
\W匹配非字母、数字、下划线. 等价于 [^A-Za-z0-9_].
\xn匹配 n, 其中 n 为十六进制转义值. 十六进制转义值必须为确定的两个数字长.
例如,\x41 匹配 A. \x041 则等价于 \x04 拼接 1. 正则表达式中可以使用 ASCII 编码.
\num匹配 num, 其中 num 是一个正整数.
对所获取的匹配的引用. 例如,(.)\1 匹配两个连续的相同字符.
参见 向后引用的例子 .
\n标识一个八进制转义值或一个向后引用.
如果 \n 之前至少 n 个获取的子表达式, 则 n向后引用; 否则, 如果 n 为八进制数字 (0-7), 则 n 为一个 八进制转义值.
\nm标识一个八进制转义值或一个向后引用.
如果 \nm 之前至少有 nm 个获得子表达式, 则 nm向后引用; 如果 \nm 之前至少有 n 个获取, 则 n 为一个 后跟文字 m 的向后引用; 如果前面的条件都不满足, 若 nm 均为八进制数字 (0-7), 则 \nm 将匹配 八进制转义值 nm.
\nml如果 n 为八进制数字 (0-3), 且 ml 均为八进制数字 (0-7), 则匹配 八进制转义值 nml.
\un匹配 n, 其中 n 是一个 用四个十六进制数字表示的 Unicode 字符.
例如,  \u00A9 匹配版权符号 (©)

3. 题外话: 正则表达式中的运算符优先级

正则表达式从左到右进行计算, 并遵循优先级顺序, 这与算术表达式非常类似.

相同优先级的从左到右进行运算, 不同优先级的运算先高后低. 下表从最高到最低说明了各种正则表达式运算符的优先级顺序:

运算符描述
\转义符
(), (?:), (?=), []圆括号和方括号
*, +, ?, {n}, {n,}, {n,m}限定符
^, $, \任何元字符任何字符定位点和序列 (即: 位置和顺序)
|替换, “或” 操作
字符具有高于替换运算符的优先级, 使得 m|food 匹配 m 或 food. 若要匹配 mood 或 food, 请使用括号创建子表达式, 从而产生 (m|f)ood.

4. 举一反三

4.1. 举例与思考

例8:

一个合理的用户名正则表达式: 大小写英文, 数字, 下划线, 连字符. 因此可写为

^[a-zA-Z0-9_-]+$

若用户名只能以英文和下划线开头, 则可写为

^[a-zA-Z_][a-zA-Z0-9_-]*$

思考:

若继续限制用户名长度为4到32位, 则应该如何写?


例9:

包含中文的正则为

[\u4E00-\u9FA5]

思考:

魔兽世界国服为角色起名时, 必须为其中一种情况:

  • 首字母大写的12位英文
  • 6位中文

请写出对应的正则表达式.

4.2. 课后作业

作业0:

复习相关知识点, 看懂例题, 认真思考, 准备答疑.

作业1:

下面是某种判断是否为合法电子邮件地址的正则表达式, 请按部就班分析为什么要这么写:

^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$

可点击 此地址, 通过可视化工具辅助分析.

作业2:

对某密码强度要求如下:

  • 至少6位
  • 至少一个大写字母
  • 至少一个小写字母
  • 至少一个数字
  • 至少一个特殊字符 (!@#$%^&*? )

下面是该密码强度的正则表达式, 感受 正向肯定预查 的奇妙之处:

^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$

可点击 此地址, 通过可视化工具辅助分析.

作业3:

php官方文档中, 对于 “数字字符串” 的描述是这样的:

如果一个 PHP string 可以被解释为 int 或 float 类型,则它被视为数字字符串。

PHP 8.0.0 正式可用:

WHITESPACES      \s*
LNUM             [0-9]+
DNUM             ([0-9]*)[\.]{LNUM}) | ({LNUM}[\.][0-9]*)
EXPONENT_DNUM    (({LNUM} | {DNUM}) [eE][+-]? {LNUM})
INT_NUM_STRING   {WHITESPACES} [+-]? {LNUM} {WHITESPACES}
FLOAT_NUM_STRING {WHITESPACES} [+-]? ({DNUM} | {EXPONENT_DNUM}) {WHITESPACES}
NUM_STRING       ({INT_NUM_STRING} | {FLOAT_NUM_STRING})

请跟据文档中的描述, 理解并举例哪些属于数字字符串, 哪些不属于.

作业4:

请尝试写出正则表达式来判断某个文本是否为合法RGB颜色的十六进制表达.

下面是几种可能的合法情况:

  • #b8b8b8
  • bcdeff
  • #3C4D5E
  • #666
  • DDD
  • #c0e

作业5:

写出24小时制的时分秒正则表达式, 时分秒中间用英文冒号隔开, 且均为两位, 不足需补0.

作业6:

请写出用于校验金额输入合法性的正则表达式. 要求金额可正可负, 可整可零, 小数位最多两位.

下面是几种可能的合法情况:

  • 123.04
  • 1.30
  • -0.5
  • 0
  • 16658.4
  • 21,474,836.47

作业7:

请尽可能严格地写出一个用于校验IPv4合法性的正则表达式.

作业8:

请尽可能严格且全面地写出一个用于校验身份证号码合法性的正则表达式, 要求兼容15位和18位.

5. 参考资料

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注