正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。比如处理网页文本的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。模式用于描述在搜索文本时要匹配的一个或多个字符串。
基础
举个栗子
比如需要匹配 we 单词,可以直接使用正则表达式:we,这是最简单的正则表达式,可以精确匹配英文文献中的we单词。
正则表达式工具一般可以设置为忽略大小写,那 we 可以将文献中的We、wE、we和WE都匹配出来。如果仅仅使用we来匹配,会发现得出来的结果和预想的不一样,类似于well、welcome这样的单词也会被匹配出来,因为这些单词中也包含we。
如何仅仅将we单词匹配出来呢?我们需要使用这样的正则表达式:\bwe\b。“\b”是正则表达式规定的一个特殊代码,被称为元字符,代表着单词的开头或结尾,也就是单词的分界处,它本身不代表英语中空格、标点符号、换行等单词分隔符,只是用来匹配一个位置。
如果我们需要匹配 we、work 和它们之间的所有内容,那么我们可以使用另外两个元字符 “.” 和 “*” ,正则表达式可以写为\bwe\b.*\bwork\b。
分开解释一下每一部分的意思:
“.” 匹配除了换行符的任意字符
“*” 元字符不是代表字符,而是代表数量,含义是“*”前面的内容可以连续重复任意次。
“.*” 整体的意思表示可以匹配任意数量不换行的字符。
所以,\bwe\b.*\bwork\b 表示匹配 we、work 和它们之间的任意数量的字符。
常用元字符
常用的元字符包括一下几种:
元字符 | 含义 |
---|---|
. | 匹配除换行符以外的任意字符 |
\b | 匹配单词的开始或结束 |
\d | 匹配数字 |
\w | 匹配字母、数字、下划线或汉字 |
\s | 匹配任意空白符,包括空格、制表符、换行符、中文全角空格等 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
再举个栗子?
比如需要搜索出以 abc 开头后面是数字的字符,比如 abc123,可以使用abc\d*
但是如果要匹配元字符中不存在的字符集,例如匹配 a、b、c、d 和 e 中任意一个字符,这时候就需要自定义字符集合。正则表达式是通过[ ]来实现自定义字符集合,[abcde]就是匹配abcde中的任意一个字符,[.?!] 匹配标点符号(“.”、“? ”或“! ”)。
除了列举每个字符,还可以指定一个字符范围。
[0-9]代表的含义与“\d”是完全一致的。
[a-z0-9A-Z_]也等同于“\w”,代表着26个字母中的大小写、0~9的数字和下划线中的任一个字符(不考虑中文)。
字符转义
如果你想查找元字符本身的话,比如 “.” 或者 “*”。这个时候就需要用到转义,使用 “\” 来取消这些字符的特殊意义。因此如果查找 “.” 、“\” 或者 “*” 时,必须写成 “\.”、 “\\” 和 “\*”。例如匹配www.baidu.com这个网址时,可以表达式可以写为www\.baidu\.com。
重复限定符
限定符 | 含义 |
---|---|
* | 重复零次或多次 |
+ | 重复一次或多次 |
? | 重复零次或一次 |
{n} | 重复 n 次 |
{n,} | 重复 n 次或更多 |
{n, m} | 重复 n 次至 m 次 |
* 前面已经使用过,再举个其他栗子??
^\d{5,12}$:匹配5到12个数字的字符串。
分支条件
如果需要满足很多条件中的任意一种规则,可以用 “|” 把不同的规则分隔开。例如匹配身份证号(15位、18位数字):^\d{15}|\d{18}$
匹配分支条件时,将会从左到右地测试每个条件,如果满足了某个分支的话,就不会去再管其他条件了,条件之间是一种或的关系,例如从1234567890匹配出连续的4个数字或者连续8个数字,如果写成\d{4}|\d{8},其实\d{8}是失效的,既然能匹配出来8位数字,肯定就能匹配出4位数字。
反义
有时需要查找除某一类字符集合之外的字符。比如想查找除了数字以外,包含其他任意字符的情况。
元字符 | 含义 |
---|---|
\B | 匹配不是单词的开始或结束的位置 |
\D | 匹配任意非数字的字符 |
\W | 匹配任意不是字母、数字、下划线或汉字的字符 |
\S | 匹配任意不是空白符的字符 |
[^a] | 匹配除了 a 以外的任意字符 |
[^abcde] | 匹配除了 a, b, c, d, e 以外的任意字符 |
[^(123 | abc)] |
高级用法
贪婪与懒惰
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符,这就是贪婪模式。以表达式a\w+b为例,如果搜索a12b34b,会尽可能匹配更多的个数,最后就会匹配整个a12b34b,而不是a12b。但是如果想匹配出a12b怎么办呢?这时候就需要使用懒惰模式,尽可能匹配个数较少的情况,因此需要将上面的a\w+b表达式改为a\w+? b,使用 “?” 来启用懒惰模式。
语法 | 含义 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复一次或多次,但尽可能少重复 |
?? | 重复零次或一次,但尽可能少重复 |
{n,}? | 重复 n 次以上,但尽可能少重复 |
{n, m}? | 重复 n 次至 m 次,,但尽可能少重复 |
分组和捕获
如果要对多个字符进行重复匹配,需要使用分组,我们可以使用小括号()来指定要重复的子表达式,然后对这个子表达式进行重复,例如:(abc)? 表示0个或1个abc 这里一个括号的表达式就表示一个分组。分组可以分为两种形式,捕获组和非捕获组。
捕获组
捕获组可以通过从左到右计算其开括号来编号。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。例如,在表达式(A)(B(D)) 中,存在四组,(A)(B(D)),(A),(B(D)),(D),对于分组而言,整个表达式永远算作第0组。
比如对于IP地址的匹配,简单的可以写为如下形式:
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}
但仔细观察,我们可以发现一定的规律,可以把.\d{1,3}看成一个整体,也就是把他们看成一组,再把这个组重复3次即可。表达式如下:
\d{1,3}(.\d{1,3}){3}
另外,可以使用 Back 引用 是说在后面的表达式中我们可以使用组的编号来引用前面的表达式所捕获到的文本序列。
就拿匹配
<(title)>.*</\1>
非捕获组
以 (?) 开头的组是非捕获组,它不捕获文本,也不针对组合进行计数。就是说,如果小括号中以?号开头,那么这个分组就不会捕获文本,当然也不会有组的编号,因此也不存在后向引用。
捕获组捕获的内容是被存储在内存中,可供以后使用,比如反向引用就是引用的内存中存储的捕获组中捕获的内容。而非捕获组则不会捕获文本,也不会将它匹配到的内容单独分组来放到内存中。所以,使用非捕获组较使用捕获组更节省内存。比如:(?:a|A)123(?:b)可以匹配 a123b 或者 A123b
分类 | 语法 | 含义 |
---|---|---|
(exp) | 匹配 exp,并捕获文本到自动命名的组里 | |
捕获 | (?exp) | 匹配 exp,并捕获文本到名称为 name 的组里,也可以写成 (?'name’exp) |
(?:exp) | 匹配 exp,不捕获匹配的文本,也不给此分组分配组号 | |
(?=exp) | 匹配 exp 前面的位置 | |
零宽断言 | (?<=exp) | 匹配 exp 后面的位置 |
(?!exp) | 匹配后面跟的不是 exp 的位置 | |
(?<!exp) | 匹配前面不是 exp 的位置 | |
注释 | (?#comment) | 对正则表达式的处理没有任何影响,只是用于提供注释让人阅读 |
零宽断言
零宽断言总共有四种形式。前两种是正向零宽断言,后两种是负向零宽断言。
元字符“\b”、“^”匹配的是一个位置,而且这个位置需要满足一定的条件,我们把这个条件称为断言或零宽度断言。断言用来声明一个应该为真的事实,正则表达式中只有当断言为真时才会继续进行匹配。
- (? =exp)叫零宽度正预测先行断言,它断言此位置的后面能匹配表达式exp。比如[a-z]*(? =ing)匹配以ing结尾的单词的前面部分(除了ing以外的部分),查找I love cooking and singing时会匹配出中的cook与sing。先行断言的执行步骤应该是从要匹配字符的最右端找到第一个“ing”,再匹配前面的表达式,如无法匹配则查找第二个“ing”。
- (? <=exp)叫零宽度正回顾后发断言,它断言此位置的前面能匹配表达式exp。比如(? <=abc).*匹配以abc开头的字符串的后面部分,可以匹配abcdefgabc中的defgabc而不是abcdefg。通过比较很容易看出后发断言和先行断言正好相反:它先从要匹配的字符串的最左端开始查找断言表达式,之后再匹配后面的字符串,如果无法匹配则继续查找第二个断言表达式。
负向零宽断言的两种形式(把 = 换成 !):
- (? ! exp)叫零宽度负预测先行断言,断言此位置的后面不能匹配表达式exp。比如\b((? ! abc)\w)+\b匹配不包含连续字符串abc的单词,查找“abc123, ade123”这个字符串,可以匹配出ade123。
- (? <! exp)叫零宽度负回顾后发断言,断言此位置的前面不能匹配表达式exp。比如(? <! [a-z])\d{7}匹配前面不是小写字母的七位数字。还有一个复杂的例子:(? <=<(\w+)>).*(? =</\1>),用于匹配不包含属性的简单HTML标记内的内容。比如可以从
test中提取出“test”。
再举个栗子,正则表达式 (?<!4)56(?=9),匹配后面的文本56前面不能是4,后面必须是9组成。因此,可以匹配如下文本 5569 ,与4569不匹配。
注释
正则表达式可以包含注释进行解释说明,通过语法(? #comment)来实现,例如\b\w+(? #字符串)\b。要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意地添加空格、Tab、换行,而实际使用时这些都将被忽略。