[TOC]
什么是正则表达式?
正则表达式是一组由字母和符号组成的特殊文本, 它可以用来从文本中找出满足你想要的格式的句子.
一个正则表达式是在一个主体字符串中从左到右匹配字符串时的一种样式.
"Regular expression"这个词比较拗口, 我们常使用缩写的术语"regex"或"regexp".
正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等.
想象你正在写一个应用, 然后你想设定一个用户命名的规则, 让用户名包含字符,数字,下划线和连字符,以及限制字符的个数,好让名字看起来没那么丑.
我们使用以下正则表达式来验证一个用户名:
[图片上传失败...(image-7761fb-1532334994453)]
以上的正则表达式可以接受 john_doe
, jo-hn_doe
, john12_as
.
但不匹配Jo
, 因为它包含了大写的字母而且太短了.
1. 基本匹配
正则表达式其实就是在执行搜索时的格式, 它由一些字母和数字组合而成.
例如: 一个正则表达式 the
, 它表示一个规则: 由字母t
开始,接着是h
,再接着是e
.
<pre>
"the" => The fat cat sat on <a href="#learn-regex"><strong>the</strong></a> mat.
</pre>
正则表达式123
匹配字符串123
. 它逐个字符的与输入的正则表达式做比较.
正则表达式是大小写敏感的, 所以The
不会匹配the
.
<pre>
"The" => <a href="#learn-regex"><strong>The</strong></a> fat cat sat on the mat.
</pre>
2. 元字符
正则表达式主要依赖于元字符.
元字符不代表他们本身的字面意思, 他们都有特殊的含义. 一些元字符写在方括号中的时候有一些特殊的意思. 以下是一些元字符的介绍:
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符. |
[ ] | 字符种类. 匹配方括号内的任意字符. |
[^ ] | 否定的字符种类. 匹配除了方括号里的任意字符 |
* | 匹配>=0个重复的在*号之前的字符. |
+ | 匹配>=1个重复的+号前的字符. |
? | 标记?之前的字符为可选. |
{n,m} | 匹配num个大括号之前的字符 (n <= num <= m). |
(xyz) | 字符集, 匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. |
\ | 转义字符,用于匹配一些保留的字符 <code>[ ] ( ) { } . * + ? ^ $ \ |</code> |
^ | 从开始行开始匹配. |
$ | 从末端开始匹配. |
2.1 点运算符 .
.
是元字符中最简单的例子.
.
匹配任意单个字符, 但不匹配换行符.
例如, 表达式.ar
匹配一个任意字符后面跟着是a
和r
的字符串.
<pre>
".ar" => The <a href="#learn-regex"><strong>car</strong></a> <a href="#learn-regex"><strong>par</strong></a>ked in the <a href="#learn-regex"><strong>gar</strong></a>age.
</pre>
2.2 字符集
字符集也叫做字符类.
方括号用来指定一个字符集.
在方括号中使用连字符来指定字符集的范围.
在方括号中的字符集不关心顺序.
例如, 表达式[Tt]he
匹配 the
和 The
.
<pre>
"[Tt]he" => <a href="#learn-regex"><strong>The</strong></a> car parked in <a href="#learn-regex"><strong>the</strong></a> garage.
</pre>
方括号的句号就表示句号.
表达式 ar[.]
匹配 ar.
字符串
<pre>
"ar[.]" => A garage is a good place to park a c<a href="#learn-regex"><strong>ar.</strong></a>
</pre>
2.2.1 否定字符集
一般来说 ^
表示一个字符串的开头, 但它用在一个方括号的开头的时候, 它表示这个字符集是否定的.
例如, 表达式[^c]ar
匹配一个后面跟着ar
的除了c
的任意字符.
<pre>
"[^c]ar" => The car <a href="#learn-regex"><strong>par</strong></a>ked in the <a href="#learn-regex"><strong>gar</strong></a>age.
</pre>
2.3 重复次数
后面跟着元字符 +
, *
or ?
的, 用来指定匹配子模式的次数.
这些元字符在不同的情况下有着不同的意思.
2.3.1 *
号
*
号匹配 在*
之前的字符出现大于等于0
次.
例如, 表达式 a*
匹配以0或更多个a开头的字符, 因为有0个这个条件, 其实也就匹配了所有的字符. 表达式[a-z]*
匹配一个行中所有以小写字母开头的字符串.
<pre>
"[a-z]*" => T<a href="#learn-regex"><strong>he</strong></a> <a href="#learn-regex"><strong>car</strong></a> <a href="#learn-regex"><strong>parked</strong></a> <a href="#learn-regex"><strong>in</strong></a> <a href="#learn-regex"><strong>the</strong></a> <a href="#learn-regex"><strong>garage</strong></a> #21.
</pre>
*
字符和.
字符搭配可以匹配所有的字符.*
.
*
和表示匹配空格的符号\s
连起来用, 如表达式\s*cat\s*
匹配0或更多个空格开头和0或更多个空格结尾的cat字符串.
<pre>
"\scat\s" => The fat<a href="#learn-regex"><strong> cat </strong></a>sat on the <a href="#learn-regex">con<strong>cat</strong>enation</a>.
</pre>
2.3.2 +
号
+
号匹配+
号之前的字符出现 >=1 次.
例如表达式c.+t
匹配以首字母c
开头以t
结尾,中间跟着任意个字符的字符串.
<pre>
"c.+t" => The fat <a href="#learn-regex"><strong>cat sat on the mat</strong></a>.
</pre>
2.3.3 ?
号
在正则表达式中元字符 ?
标记在符号前面的字符为可选, 即出现 0 或 1 次.
例如, 表达式 [T]?he
匹配字符串 he
和 The
.
<pre>
"[T]he" => <a href="#learn-regex"><strong>The</strong></a> car is parked in the garage.
</pre>
<pre>
"[T]?he" => <a href="#learn-regex"><strong>The</strong></a> car is parked in t<a href="#learn-regex"><strong>he</strong></a> garage.
</pre>
2.4 {}
号
在正则表达式中 {}
是一个量词, 常用来一个或一组字符可以重复出现的次数.
例如, 表达式 [0-9]{2,3}
匹配最少 2 位最多 3 位 0~9 的数字.
<pre>
"[0-9]{2,3}" => The number was 9.<a href="#learn-regex"><strong>999</strong></a>7 but we rounded it off to <a href="#learn-regex"><strong>10</strong></a>.0.
</pre>
我们可以省略第二个参数.
例如, [0-9]{2,}
匹配至少两位 0~9 的数字.
如果逗号也省略掉则表示重复固定的次数.
例如, [0-9]{3}
匹配3位数字
<pre>
"[0-9]{2,}" => The number was 9.<a href="#learn-regex"><strong>9997</strong></a> but we rounded it off to <a href="#learn-regex"><strong>10</strong></a>.0.
</pre>
<pre>
"[0-9]{3}" => The number was 9.<a href="#learn-regex"><strong>999</strong></a>7 but we rounded it off to 10.0.
</pre>
2.5 (...)
特征标群
特征标群是一组写在 (...)
中的子模式. 例如之前说的 {}
是用来表示前面一个字符出现指定次数. 但如果在 {}
前加入特征标群则表示整个标群内的字符重复 N 次. 例如, 表达式 (ab)*
匹配连续出现 0 或更多个 ab
.
我们还可以在 ()
中用或字符 |
表示或. 例如, (c|g|p)ar
匹配 car
或 gar
或 par
.
<pre>
"(c|g|p)ar" => The <a href="#learn-regex"><strong>car</strong></a> is <a href="#learn-regex"><strong>par</strong></a>ked in the <a href="#learn-regex"><strong>gar</strong></a>age.
</pre>
2.6 |
或运算符
或运算符就表示或, 用作判断条件.
例如 (T|t)he|car
匹配 (T|t)he
或 car
.
<pre>
"(T|t)he|car" => <a href="#learn-regex"><strong>The</strong></a> <a href="#learn-regex"><strong>car</strong></a> is parked in <a href="#learn-regex"><strong>the</strong></a> garage.
</pre>
2.7 转码特殊字符
反斜线 \
在表达式中用于转码紧跟其后的字符. 用于指定 { } [ ] / \ + * . $ ^ | ?
这些特殊字符. 如果想要匹配这些特殊字符则要在其前面加上反斜线 \
.
例如 .
是用来匹配除换行符外的所有字符的. 如果想要匹配句子中的 .
则要写成 \.
以下这个例子 \.?
是选择性匹配.
<pre>
"(f|c|m)at.?" => The <a href="#learn-regex"><strong>fat</strong></a> <a href="#learn-regex"><strong>cat</strong></a> sat on the <a href="#learn-regex"><strong>mat.</strong></a>
</pre>
2.8 锚点
在正则表达式中, 想要匹配指定开头或结尾的字符串就要使用到锚点. ^
指定开头, $
指定结尾.
2.8.1 ^
号
^
用来检查匹配的字符串是否在所匹配字符串的开头.
例如, 在 abc
中使用表达式 ^a
会得到结果 a
. 但如果使用 ^b
将匹配不到任何结果. 因为在字符串 abc
中并不是以 b
开头.
例如, ^(T|t)he
匹配以 The
或 the
开头的字符串.
<pre>
"(T|t)he" => <a href="#learn-regex"><strong>The</strong></a> car is parked in <a href="#learn-regex"><strong>the</strong></a> garage.
</pre>
<pre>
"^(T|t)he" => <a href="#learn-regex"><strong>The</strong></a> car is parked in the garage.
</pre>
2.8.2 $
号
同理于 ^
号, $
号用来匹配字符是否是最后一个.
例如, (at\.)$
匹配以 at.
结尾的字符串.
<pre>
"(at.)" => The fat c<a href="#learn-regex"><strong>at.</strong></a> s<a href="#learn-regex"><strong>at.</strong></a> on the m<a href="#learn-regex"><strong>at.</strong></a>
</pre>
<pre>
"(at.)$" => The fat cat. sat. on the m<a href="#learn-regex"><strong>at.</strong></a>
</pre>
3. 简写字符集
正则表达式提供一些常用的字符集简写. 如下:
简写 | 描述 |
---|---|
. | 除换行符外的所有字符 |
\w | 匹配所有字母数字, 等同于 [a-zA-Z0-9_]
|
\W | 匹配所有非字母数字, 即符号, 等同于: [^\w]
|
\d | 匹配数字: [0-9]
|
\D | 匹配非数字: [^\d]
|
\s | 匹配所有空格字符, 等同于: [\t\n\f\r\p{Z}]
|
\S | 匹配所有非空格字符: [^\s]
|
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
\p | 匹配 CR/LF (等同于 \r\n ),用来匹配 DOS 行终止符 |
4. 前后关联约束(前后预查)
前置约束和后置约束都属于非捕获簇(用于匹配不在匹配列表中的格式).
前置约束用于判断所匹配的格式是否在另一个确定的格式之后.
例如, 我们想要获得所有跟在 $
符号后的数字, 我们可以使用正向向后约束 (?<=\$)[0-9\.]*
.
这个表达式匹配 $
开头, 之后跟着 0,1,2,3,4,5,6,7,8,9,.
这些字符可以出现大于等于 0 次.
前后关联约束如下:
符号 | 描述 |
---|---|
?= | 前置约束-存在 |
?! | 前置约束-排除 |
?<= | 后置约束-存在 |
?<! | 后置约束-排除 |
4.1 ?=...
前置约束(存在)
?=...
前置约束(存在), 表示第一部分表达式必须跟在 ?=...
定义的表达式之后.
返回结果只满足第一部分表达式.
定义一个前置约束(存在)要使用 ()
. 在括号内部使用一个问号和等号: (?=...)
.
前置约束的内容写在括号中的等号后面.
例如, 表达式 (T|t)he(?=\sfat)
匹配 The
和 the
, 在括号中我们又定义了前置约束(存在) (?=\sfat)
,即 The
和 the
后面紧跟着 (空格)fat
.
<pre>
"(T|t)he(?=\sfat)" => <a href="#learn-regex"><strong>The</strong></a> fat cat sat on the mat.
</pre>
4.2 ?!...
前置约束-排除
前置约束-排除 ?!
用于筛选所有匹配结果, 筛选条件为 其后不跟随着定义的格式
前置约束-排除
定义和 前置约束(存在)
一样, 区别就是 =
替换成 !
也就是 (?!...)
.
表达式 (T|t)he(?!\sfat)
匹配 The
和 the
, 且其后不跟着 (空格)fat
.
<pre>
"(T|t)he(?!\sfat)" => The fat cat sat on <a href="#learn-regex"><strong>the</strong></a> mat.
</pre>
4.3 ?<= ...
后置约束-存在
后置约束-存在 记作(?<=...)
用于筛选所有匹配结果, 筛选条件为 其前跟随着定义的格式.
例如, 表达式 (?<=(T|t)he\s)(fat|mat)
匹配 fat
和 mat
, 且其前跟着 The
或 the
.
<pre>
"(?<=(T|t)he\s)(fat|mat)" => The <a href="#learn-regex"><strong>fat</strong></a> cat sat on the <a href="#learn-regex"><strong>mat</strong></a>.
</pre>
4.4 ?<!...
后置约束-排除
后置约束-排除 记作 (?<!...)
用于筛选所有匹配结果, 筛选条件为 其前不跟着定义的格式.
例如, 表达式 (?<!(T|t)he\s)(cat)
匹配 cat
, 且其前不跟着 The
或 the
.
<pre>
"(?<!(T|t)he\s)(cat)" => The cat sat on <a href="#learn-regex"><strong>cat</strong></a>.
</pre>
5. 标志
标志也叫修饰语, 因为它可以用来修改表达式的搜索结果.
这些标志可以任意的组合使用, 它也是整个正则表达式的一部分.
标志 | 描述 |
---|---|
i | 忽略大小写. |
g | 全局搜索. |
m | 多行的: 锚点元字符 ^ $ 工作范围在每行的起始. |
5.1 忽略大小写 (Case Insensitive)
修饰语 i
用于忽略大小写.
例如, 表达式 /The/gi
表示在全局搜索 The
, 在后面的 i
将其条件修改为忽略大小写, 则变成搜索 the
和 The
, g
表示全局搜索.
<pre>
"The" => <a href="#learn-regex"><strong>The</strong></a> fat cat sat on the mat.
</pre>
<pre>
"/The/gi" => <a href="#learn-regex"><strong>The</strong></a> fat cat sat on <a href="#learn-regex"><strong>the</strong></a> mat.
</pre>
5.2 全局搜索 (Global search)
修饰符 g
常用语执行一个全局搜索匹配, 即(不仅仅返回第一个匹配的, 而是返回全部).
例如, 表达式 /.(at)/g
表示搜索 任意字符(除了换行) + at
, 并返回全部结果.
<pre>
"/.(at)/" => The <a href="#learn-regex"><strong>fat</strong></a> cat sat on the mat.
</pre>
<pre>
"/.(at)/g" => The <a href="#learn-regex"><strong>fat</strong></a> <a href="#learn-regex"><strong>cat</strong></a> <a href="#learn-regex"><strong>sat</strong></a> on the <a href="#learn-regex"><strong>mat</strong></a>.
</pre>
5.3 多行修饰符 (Multiline)
多行修饰符 m
常用语执行一个多行匹配.
像之前介绍的 (^,$)
用于检查格式是否是在待检测字符串的开头或结尾. 但我们如果想要它在每行的开头和结尾生效, 我们需要用到多行修饰符 m
.
例如, 表达式 /at(.)?$/gm
表示在待检测字符串每行的末尾搜索 at
后跟一个或多个 .
的字符串, 并返回全部结果.
<pre>
"/.at(.)?$/" => The fat
cat sat
on the <a href="#learn-regex"><strong>mat.</strong></a>
</pre>
<pre>
"/.at(.)?$/gm" => The <a href="#learn-regex"><strong>fat</strong></a>
cat <a href="#learn-regex"><strong>sat</strong></a>
on the <a href="#learn-regex"><strong>mat.</strong></a>
</pre>
最短匹配
懒惰限定符
| 代码 | 说明 |
| :-- | :-- |
| *? | 重复任意次,但尽可能少重复 |
| ?? | 重复0次或1次,但尽可能少重复 |
| {n,m}? | 重复n到m次,但尽可能少重复 |
| {n,}? | 重复n次以上,但尽可能少重复 |
分组捕获
捕获组
捕获组可以通过从左到右计算其开括号来编号 。例如,在表达式 (A)(B(C))
中,存在四个这样的组:
| 组号 | 捕获组 |
| :-- | :-- |
| 0 | (A)(B(C)) |
| 1 | (A) |
| 2 | (B(C)) |
| 3 | (C) |
组零 始终代表 整个表达式
之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用(反向引用) 在表达式中使用,也可以在匹配操作完成后从匹配器检索。
Back 引用 是说在后面的表达式中我们可以使用 组的编号 来引用前面的表达式所捕获到的文本序列。注意:反向引用,引用的是前面捕获组中的 文本 而不是正则,也就是说反向引用处匹配的文本应和前面捕获组中的文本相同,这一点很重要。
【例】 (["']).*\1
例子中使用了分组,\1
就是对引号这个分组的引用,它匹配包含在两个引号或者两个单引号中的所有字符串,如,"abc"
或 " ' "
或 ' " '
,但是请注意,它并不会对 " a'
或者 'a"
匹配。原因上面已经说明, Back引用只是引用文本而不是表达式
非捕获组
它的作用就是匹配 Pattern 字符,好处就是不捕获文本,不将匹配到的字符存储到内存中,从而节省内存。
【例】匹配 indestry 或者 indestries
我们可以使用 indestr(y|ies)
或者 indestr(?:y|ies)
【例】 (?:a|A)123(?:b)
可以匹配 a123b 或者 A123b
非捕获组有很多种形式,其中包括: 零宽度断言 和 模式修正符
零宽度断言
| 模式 | 说明 |
| :-- | :-- |
| (?=X) | X,通过零宽度的正 lookahead |
| (?!X) | X,通过零宽度的负 lookahead |
| (?<=X) | X,通过零宽度的正 lookbehind |
| (?<!X) | X,通过零宽度的负 lookbehind |
这四个非捕获组用于匹配表达式 X,但是不包含表达式的文本。
| 模式 | 说明 |
| :--- | :-- |
| (?=X) | 零宽度正先行断言。仅当子表达式 X 在 此位置的右侧匹配时才继续匹配。也就是说要使此零宽度断言起到我们想要的效果的话,就必须把这个非捕获组放在整个表达式的右侧。例如,/w+(?=/d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。 |
| (?!X) | 零宽度负先行断言。仅当子表达式 X 不在 此位置的右侧匹配时才继续匹配。例如,例如,/w+(?!/d) 与后不跟数字的单词匹配,而不与该数字匹配 。 |
| (?<=X) | 零宽度正后发断言。仅当子表达式 X 在 此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。 |
| (?<!X) | 零宽度负后发断言。仅当子表达式 X 不在此位置的左侧匹配时才继续匹配。例如,(?<!19)99 与不跟在 19 后面的 99 的实例匹配 |
上面都是理论性的介绍,这里就使用一些例子来说明一下问题:
【例1】正则表达式 (?<!4)56(?=9)
含义:匹配后面的文本56前面不能是4,后面必须是9组成。因此,可以匹配如下文本 5569中的 56 ,与4569不匹配。
【例2】提取字符串 da12bka3434bdca4343bdca234bm 中包含在字符a和b之间的数字,但是这个a之前的字符不能是c;b后面的字符必须是d才能提取。
显然,这里就只有3434这个数字满足要求。那么我们怎么提取呢?
首先,我们写出含有捕获组的正则表达式: [^c]a\d*bd
然后我们再将其变为非捕获组的正则表达式: (?<=[^c]a)\d*(?=bd)
模式修正符
以(?)开头的非捕获组除了零宽度断言之外,还有模式修正符。
正则表达式中常用的模式修正符有i、g、m、s、x、e等。它们之间可以组合搭配使用。
(?imnsx-imnsx: )
应用或禁用子表达式中指定的选项。例如,(?i-s: )
将打开不区分大小写并禁用单行模式。关闭不区分大小写的开关可以使用 (?-i)
。有关更多信息,请参阅正则表达式选项。
【例1】 (?i)ab
表示对 (?i)
后的所有字符都开启不区分大小写的开关。故它可以匹配ab、aB、Ab、AB
【例2】 (?i:a)b
它表示只对a开启不区分大小写的开关。故它可以匹配ab和Ab。不能匹配aB和AB。
(?>Pattern)等同于侵占模式
匹配成功不进行回溯,这个比较复杂,与侵占量词“+”可以通用,比如:\d++
可以写为 (?>\d+)
【例】将一些多位的小数截短到三位小数:\d+\.\d\d[1-9]?\d+
在这种条件下 6.625 能进行匹配,这样做没有必要,因为它本身就是三位小数。最后一个“5”本来是给 [1-9] 匹配的,但是后面还有一个 \d+ 所以,[1-9] 由于是“?”可以不匹配所以只能放弃当前的匹配,将这个“5”送给 \d+ 去匹配,如果改为: \d+\.\d\d[1-9]?+\d+
的侵占形式,在“5”匹配到 [1-9] 时,由于是侵占式的,所以不会进行回溯,后面的 \d+ 就匹配不到任东西了,所以导致 6.625 匹配失败。
这种情况,在替换时就有效了,比如把数字截短到小数点后三位,如果正好是三位小数的,就可以不用替换了,可以提高效率,侵占量词基本上就是用来提高匹配效率的。
把 \d+\.\d\d[1-9]?+\d+
改为 \d+\.\d\d(?>[1-9]?)\d+
这样是一样的。