【正则表达式】正则表达式及其应用
正则表达式
1. 什么是正则表达式?
百度百科提供的概念是这样的:正则表达式,又称规则表达式**。**(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。其通常被用来检索、替换那些符合某个模式(规则)的文本
正则表达式其实是对字符串操作的一种公式,通过组合一些具有特殊含义的字符,来实现用户的操作逻辑,从而对文本完成操作。我们一般称这些“特殊字符的组合”为规则字符串
在不同语言中,正则表达式有不同的变种,具体情况我们会在后文以JavaScript和Python为例进行说明
2. 元字符
正则表达式由一些普通字符和一些元字符(metacharacters)组成
普通字符包括大小写的字母和数字,而元字符则具有特殊的含义,相关含义如下表所示:
元字符 | 含义 |
---|---|
\ | 转义字符。例如,“n”会匹配小写字母n,但是“\n”则会匹配换行符\n |
^ | 匹配行首 |
$ | 匹配行尾 |
匹配前面的子表达式任意次(大于等于0次)。例如,zo能匹配“z”,也能匹配“zo”以及“zoo”。等价于{0,}。 | |
+ | 匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 |
? | 匹配前面的子表达式0次或1次。例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。 |
{ n} | 表示匹配规定的n次,其中n是一个非负整数。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 |
{ n,} | **表示至少匹配n次,其中n是一个非负整数。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o”。 |
{ n,m} | 表示最少匹配n次且最多匹配m次,其中m和n均为非负整数,其中n<=m。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(,+,?,{ n},{ n,},{ n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串,而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多地匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少地匹配“o”,得到结果 [‘o’, ‘o’, ‘o’, ‘o’] |
. | 匹配除“\n”和”\r”之外的任何单个字符。要匹配包括“\n”和”\r”在内的任何字符,请使用像“[\s\S]”的模式。 |
x|y | 匹配x或y。例如,“z|food”能匹配“z”或“food”。“[z|f]ood”则匹配“zood”或“food”。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”任一字符。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身. |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。 |
\b | 匹配一个单词的边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”;“\b1”可以匹配“1_23”中的“1”,但不能匹配“213”中的“1”。 |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。 |
\d | 匹配一个数字字符。等价于[0-9]。grep 要加上-P,perl正则支持 |
\D | 匹配一个非数字字符。等价于[^0-9]。grep要加上-P,perl正则支持 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何可见字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9]”,这里的”单词”字符使用Unicode字符集。 |
\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的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则nm将匹配八进制转义值nm。 |
nml | 如果n为八进制数字(0-7),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。 |
\p{P} | 小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。中括号内的“P”表示Unicode 字符集七个字符属性之一:标点字符。其他六个属性:L:字母;M:标记符号(一般不会单独出现);Z:分隔符(比如空格、换行等);S:符号(比如数学符号、货币符号等);N:数字(比如阿拉伯数字、罗马数字等);C:其他字符。注:此语法部分语言不支持,例:javascript。 |
<> | 匹配词(word)的开始(<)和结束(>)。例如正则表达式<the>能够匹配字符串”for the wise”中的”the”,但是不能匹配字符串”otherwise”中的”the”。注意:这个元字符不是所有的软件都支持的。 |
( ) | 将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到\9 的符号来引用。 |
| | 将两个匹配条件进行逻辑“或”(or)运算。例如正则表达式(him|her) 匹配”it belongs to him”和”it belongs to her”,但是不能匹配”it belongs to them.”。注意:这个元字符不是所有的软件都支持的。 |
在正则表达式中,如果直接给出字符,就是精确匹配。用\d
可以匹配一个数字,\w
可以匹配一个字母或数字,所以:
'00\d'
可以匹配'007'
,但无法匹配'00A'
;'\d\d\d'
可以匹配'010'
;'\w\w\d'
可以匹配'py3'
;
.
可以匹配任意字符,所以:
'py.'
可以匹配'pyc'
、'pyo'
、'py!'
等等。
要匹配变长的字符,在正则表达式中,用*
表示任意个字符(包括0个),用+
表示至少一个字符,用?
表示0个或1个字符,用{n}
表示n个字符,用{n,m}
表示n-m个字符:
来看一个复杂的例子:\d{3}\s+\d{3,8}
。
我们来从左到右解读一下:
\d{3}
表示匹配3个数字,例如'010'
;\s
可以匹配一个空格(也包括Tab等空白符),所以\s+
表示至少有一个空格,例如匹配' '
,' '
等;\d{3,8}
表示3-8个数字,例如'1234567'
。
综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
如果要匹配'010-12345'
这样的号码呢?由于'-'
是特殊字符,在正则表达式中,要用'\'
转义,所以,上面的正则是\d{3}\-\d{3,8}
。
但是,仍然无法匹配'010 - 12345'
,因为带有空格。所以我们需要更复杂的匹配方式。
要做更精确地匹配,可以用[]
表示范围,比如:
[0-9a-zA-Z\_]
可以匹配一个数字、字母或者下划线;[0-9a-zA-Z\_]+
可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100'
,'0_Z'
,'Py3000'
等等;[a-zA-Z\_][0-9a-zA-Z\_]*
可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}
更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
A|B
可以匹配A或B,所以(P|p)ython
可以匹配'Python'
或者'python'
。
^
表示行的开头,^\d
表示必须以数字开头。
$
表示行的结束,\d$
表示必须以数字结束。
你可能注意到了,py
也可以匹配'python'
,但是加上^py$
就变成了整行匹配,就只能匹配'py'
了。
3. Python中的正则表达式
3.1 re模块
有了准备知识,我们就可以在Python中使用正则表达式了。Python提供re
模块,包含所有正则表达式的功能。由于Python的字符串本身也用\
转义,所以要特别注意:
s = 'ABC\\-001' # Python的字符串
# 对应的正则表达式字符串变成:
# 'ABC\-001'
因此我们强烈建议使用Python的r
前缀,就不用考虑转义的问题了:
s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'
先看看如何判断正则表达式是否匹配:
>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
>>>
match()
方法判断是否匹配,如果匹配成功,返回一个Match
对象,否则返回None
。常见的判断方法就是:
test = '用户输入的字符串'
if re.match(r'正则表达式', test):
print('ok')
else:
print('failed')
3.2 分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()
表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$
分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'
如果正则表达式中定义了组,就可以在Match
对象上用group()
方法提取出子串来
注意到group(0)
永远是原始字符串,group(1)
、group(2)
……表示第1、2、……个子串
提取子串非常有用。来看一个更凶残的例子:
>>> t = '19:05:30'
>>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
>>> m.groups()
('19', '05', '30')
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'
对于'2-30'
,'4-31'
这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了
3.3 贪婪匹配
最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0
:
>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')
由于\d+
采用贪婪匹配,直接把后面的0
全部匹配了,结果0*
只能匹配空字符串了
必须让\d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0
匹配出来,加个?
就可以让\d+
采用非贪婪匹配:
>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')
3.4 编译
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
- 编译正则表达式,如果正则表达式的字符串本身不合法,会报错
- 用编译后的正则表达式去匹配字符串
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')
编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串
4. JavaScrip中的正则表达式
4.1 描述字符
根据正则表达式语法规则,大部分字符仅能够描述自身,这些字符被称为普通字符,如所有的字母、数字等。
元字符就是拥有特动功能的特殊字符,大部分需要加反斜杠进行标识,以便于普通字符进行区别,而少数元字符,需要加反斜杠,以便转译为普通字符使用。JavaScript 正则表达式支持的元字符如表所示。
元字符 | 描述 |
---|---|
. | 查找单个字符,除了换行和行结束符 |
\w | 查找单词字符 |
\W | 查找非单词字符 |
\d | 查找数字 |
\D | 查找非数字字符 |
\s | 查找空白字符 |
\S | 查找非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\0 | 查找 NUL字符 |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xxx | 查找以八进制数 xxxx 规定的字符 |
\xdd | 查找以十六进制数 dd 规定的字符 |
\uxxxx | 查找以十六进制 xxxx规定的 Unicode 字符 |
表示字符的方法有多种,除了可以直接使用字符本身外,还可以使用 ASCII 编码或者 Unicode 编码来表示。
示例1
下面使用 ASCII 编码定义正则表达式直接量。
var r = /\x61/;
var s = "JavaScript";
var a = s.match(s);
由于字母 a 的 ASCII 编码为 97,被转换为十六进制数值后为 61,因此如果要匹配字符 a,就应该在前面添加“\x”前缀,以提示它为 ASCII 编码。
示例2
除了十六进制外,还可以直接使用八进制数值表示字符。
var r = /\141/;
var s = "JavaScript";
var a = s.match(r);
使用十六进制需要添加“\x”前缀,主要是为了避免语义混淆,而八进制则不需要添加前缀。
示例3
ASCII 编码只能够匹配有限的单字节字符,使用 Unicode 编码可以表示双字节字符。Unicode 编码方式:“\u”前缀加上 4 位十六进制值。
var r = "/\u0061/";
var s = "JavaScript";
var a = s.match(s);
在 RegExp() 构造函数中使用元字符时,应使用双斜杠。
var r = new RegExp("\\u0061");
RegExp() 构造函数的参数只接受字符串,而不是字符模式。在字符串中,任何字符加反斜杠还表示字符本身,如字符串“\u”就被解释为 u 本身,所以对于“\u0061”字符串来说,在转换为字符模式时,就被解释为“u0061”,而不是“\u0061”,此时反斜杠就失去转义功能。解决方法:在字符 u 前面加双反斜杠。
4.2 描述字符范围
在正则表达式语法中,放括号表示字符范围。在方括号中可以包含多个字符,表示匹配其中任意一个字符。如果多个字符的编码顺序是连续的,可以仅指定开头和结尾字符,省略中间字符,仅使用连字符~
表示。如果在方括号内添加脱字符^
前缀,还可以表示范围之外的字符。例如:
- [abc]:查找方括号内任意一个字符。
- [^abc]:查找不在方括号内的字符。
- [0-9]:查找从 0 至 9 范围内的数字,即查找数字。
- [a-z]:查找从小写 a 到小写 z 范围内的字符,即查找小写字母。
- [A-Z]:查找从大写 A 到大写 Z 范围内的字符,即查找大写字母。
- [A-z]:查找从大写 A 到小写 z 范围内的字符,即所有大小写的字母。
示例1
字符范围遵循字符编码的顺序进行匹配。如果将要匹配的字符恰好在字符编码表中特定区域内,就可以使用这种方式表示。
如果匹配任意 ASCII 字符:
var r = /[\u0000-\u00ff]/g;
如果匹配任意双字节的汉字:
var r = /[^\u0000-\u00ff]/g;
如果匹配任意大小写字母和数字:
var r = /[a-zA-Z0-9]/g;
使用 Unicode 编码设计,匹配数字:
var r = /[\u0030-\u0039]/g;
使用下面字符模式可以匹配任意大写字母:
var r = /[\u0041-\u004A]/g;
使用下面字符模式可以匹配任意小写字母:
var r = /[\u0061-\u007A]/g;
示例2
在字符范围内可以混用各种字符模式。
var s = "abcdez"; //字符串直接量
var r = /[abce-z]/g; //字符a、b、c,以及从e~z之间的任意字符
var a = s.match(r); //返回数组["a","b","c","e","z"]
示例3
在中括号内不要有空格,否则会误解为还要匹配空格。
var r = /[0-9]/g;
示例4
字符范围可以组合使用,以便设计更灵活的匹配模式。
var s = "abc4 abd6 abe3 abf1 abg7"; //字符串直接量
var r = /ab[c-g][1-7]/g; //前两个字符为ab,第三个字符为从c到g,第四个字符为1~7的任意数字
var a = s.match(r); //返回数组["abc4","abd6","abe3","abf1","abg7"]
示例5
使用反义字符范围可以匹配很多无法直接描述的字符,达到以少应多的目的。
var r = /[^0123456789]/g;
在这个正则表达式中,将会匹配除了数字以外任意的字符。反义字符类比简单字符类的功能更强大和实用。
4.3 选择匹配
选择匹配类似于 JavaScript 的逻辑与运算,使用竖线|
描述,表示在两个子模式的匹配结果中任选一个。例如:
匹配任意数字或字母
var r = /\w+|\d+/;
可以定义多重选择模式。设计方法:在多个子模式之间加入选择操作符。
var r = /(abc)|(efg)|(123)|(456)/;
为了避免歧义,应该为选择操作的多个子模式加上小括号。
示例
设计对提交的表单字符串进行敏感词过滤。先设计一个敏感词列表,然后使用竖线把它们连接在一起,定义选择匹配模式,最后使用字符串的 replace() 方法把所有敏感字符替换为可以显示的编码格式。代码如下:
var s = '<meta charset="utf-8">'; //待过滤的表单提交信息
var r = /\'|\"|\<|\>/gi; //过滤敏感字符的正则表达式
function f() {
//替换函数 把敏感字符替换为对应的网页显示的编码格式
return "&#" + arguments[0].charCodeAt(0) + ";";
}
var a =s.replace(r,f); //执行过滤替换
document.write(a); //在网页中显示正常的字符信息
console.log(a);
显示结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nr9fzaIA-1633857087626)(C:\Users\10754\AppData\Roaming\Typora\typora-user-images\image-20211010170548841.png)]
4.4 重复匹配
在正则表达式语法中,定义了一组重复类量词,如表所示。它们定义了重复匹配字符的确数或约数。
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个 n 的字符串 |
n* | 匹配任何包含零个或多个 n 的字符串 |
n? | 匹配任何包含零个或一个 n 的字符串 |
n{x} | 匹配包含 x 个 n 的序列的字符串 |
n{x,y} | 匹配包含最少 x 个、最多 y 个 n 的序列的字符串 |
n{x,} | 匹配包含至少 x 个 n 的序列的字符串 |
示例
下面结合示例进行演示说明,先设计一个字符串:
var s = "ggle gogle google gooogle goooogle gooooogle goooooogle gooooooogle goooooooogle";
如果仅匹配单词 ggle 和 gogle,可以设计:
var r = /go?gle/g;
var a = s.match(r);
量词?
表示前面字符或子表达式为可有可无,等效于:
var r = /go{0,1}gle/g;
var a = s.match(r);
如果匹配第 4 个单词 gooogle,可以设计:
var r = /go{3}gle/g;
var a = s.match(r);
等效于:
var r = /gooogle/g;
var a = s.match(r);
如果匹配第 4 个到第 6 个之间的单词,可以设计:
var r = /go{3,5}gle/g;
var a = s.match(r);如果匹配所有单词,可以设计:
var r = /go*gle/g;
var a = s.match(r);
量词*
表示前面字符或表达式可以不出现,或者重复出现任意多次。等效于:
var r = /go(0,)gle/g;
var a = s.match(r);
如果匹配包含字符“o”的所有词,可以设计:
var r = /go+gle/g;
var a = s.match(r);
量词+
表示前面字符或子表达式至少出现 1 次,最多重复次数不限。等效于:
var r = /go{1,}gle/g;
var a = s.match(r);
重复类量词总是出现在它们所作用的字符或子表达式后面。如果想作用于多个字符,需要使用小括号把它们包裹在一起形成一个子表达式。
4.4 惰性匹配
重复类量词都具有贪婪性,在条件允许的前提下,会匹配尽可能多的字符。
- ?、{n} 和 {n,m} 重复类具有弱贪婪性,表现为贪婪的有限性。
- *、+ 和 {n,} 重复类具有强贪婪性,表现为贪婪的无限性。
示例1
越是排在左侧的重复类量词匹配优先级越高。下面示例显示当多个重复类量词同时满足条件时,会在保证右侧重复类量词最低匹配次数基础上,使最左侧的重复类量词尽可能占有所有字符。
var s = "<html><head><title></title></head><body></body></html>";
var r = /(<.*>)(<.*>)/;
var a = s.match(r);//左侧表达式匹配
console.log(a[1]);
console.log(a[2]); //右侧表达式匹配“</html>”
与贪婪匹配相反,惰性匹配将遵循另一种算法:在满足条件的前提下,尽可能少的匹配字符。定义惰性匹配的方法:在重复类量词后面添加问号?限制词。贪婪匹配体现了最大化匹配原则,惰性匹配则体现最小化匹配原则。
示例2
下面示例演示了如何定义匹配模式。
var s = "<html><head><title></title></head><body></body></html>";
var r = /<.*?>/;
var a = s.match(r); //返回单个元素数组["<html>"]
在上面示例中,对于正则表达式 /<.*?>/ 来说,它可以返回匹配字符串 “<>”,但是为了能够确保匹配条件成立,在执行中还是匹配了带有 4 个字符的字符串“html”。惰性取值不能够以违反模式限定的条件而返回,除非没有找到符合条件的字符串,否则必须满足它。
针对 6 种重复类惰性匹配的简单描述如下:
- {n,m}?:尽量匹配 n 次,但是为了满足限定条件也可能最多重复 m 次。
- {n}?:尽量匹配 n 次。
- {n,}?:尽量匹配 n 次,但是为了满足限定条件也可能匹配任意次。
- ??:尽量匹配,但是为了满足限定条件也可能最多匹配 1 次,相当于 {0,1}?。
- +?:尽量匹配 1 次,但是为了满足限定条件也可能匹配任意次,相当于 {1,}?。
- *? :尽量不匹配,但是为了满足限定条件也可能匹配任意次,相当于 {0,}?。
4.5 边界量词
边界就是确定匹配模式的位置,如字符串的头部或尾部,具体说明如表所示。
量词 | 说明 |
---|---|
^ | 匹配开头,在多行检测中,会匹配一行的开头 |
| $ | 匹配结尾,在多行检测中,会匹配一行的结尾
下面代码演示如何使用边界量词。先定义字符串: |
var s = "how are you"
匹配最后一个单词
var r = /\w+$/;
var a = s.match(r); //返回数组[“you”]匹配第一个单词
var r = /^\w+/;
var a = s.match(r); //返回数组[“how”]匹配每一个单词
var r = /\w+/g;
var a = s.match(r); //返回数组[“how”,”are”,”you”]
4.6 声明词量
声明表示条件的意思。声明词量包括正向声明和反向声明两种模式。
正向声明
指定匹配模式后面的字符必须被匹配,但又不返回这些字符。语法格式如下:
匹配模式 (?= 匹配条件)
声明包含在小括号内,它不是分组,因此作为子表达式。
下面代码定义一个正前向生命的匹配模式。
var s = "one : 1; two : 2";
var r = /\w*(?==)/; //使用正前向声明,指定执行匹配必须满足的条件
var a = s.match(r); //返回数组["two"]
在上面示例中,通过?==
锚定条件,指定只有在 \w* 所能够匹配的字符后面跟随一个等号字符,才能够执行 \w* 匹配。所以,最后匹配的字符串“two”,而不是字符串“one”。
反向声明
与正向声明匹配相反,指定接下来的字符都不必被匹配。语法格式如下:
匹配模式(?! 匹配条件)
下面代码定义一个反前向生命的匹配模式。
var s = "one : 1; two : 2";
var r = /\w*(?!=)/; //使用正前向声明,指定执行匹配不必满足的条件
var a = s.match(r); //返回数组["one"]
在上面示例中,通过?!=
锚定条件,指定只有在“\w*”所能够匹配的字符后面不跟随一个等号字符,才能够执行 \w*匹配。所以,最后匹配的是字符串“one”,而不是字符串“two”。
4.7 子表达式
使用小括号可以对字符模式进行任意分组,在小括号内的字符串表示子表达式,也称为子模式。子表达式具有独立的匹配功能,保存独立的匹配结果;同时,小括号后的量词将会作用于整个子表达式。
通过分组可以在一个完整的字符模式中定义一个或多个子模式。当正则表达式成功地匹配目标字符串后,也可以从目标字符串中抽出与子模式相匹配的子内容。
示例
在下面代码中,不仅能匹配出每个变量声明,同时还抽出每个变量及其值。
var s = "ab=21, bc=45, cd=43";
var r = /(\w+)=(\d*)/g;
while (a = r.exec(s)) {
console.log(a); //返回类似["ab=21","bc=45","cd=43"]三个数组
}
4.8 反向引用
在字符模式中,后面的字符可以引用前面的子表达式。实现方法如下:
+ 数字
数字指定了子表达式在字符模式中的顺序。如“\1”引用的是第 1 个子表达式,“\2”引用的是第 2 个子表达式。
示例1
在下面代码中,通过引用前面子表达式匹配的文本,实现成组匹配字符串。
var s = "<h1>title<h1><p>text<p>";
var r = /(<\/?\w+>)\1/g;
var a = s.match(r); //返回数组["<h1>title<h1>","<p>text<p>"]
由于子表达式可以相互嵌套,它们的顺序将根据左括号的顺序来确定。例如,下面示例定义匹配模式包含多个子表达式。
var s = "abc";
var r = /(a(b(c)))/;
var a = s.match(r); //返回数组["abc","abc","bc","c"]
在这个模式中,共产生了 3 个反向引用,第一个是“(a(b©))”,第二个是“(b©)”,第三个是“©”。它们引用的匹配文本分别是字符串“abc”、“bc”和“c”。
对子表达式的引用,是指引用前面子表达式所匹配的文本,而不是子表达式的匹配模式。如果要引用前面子表达式的匹配模式,则必须使用下面方式,只有这样才能够达到匹配目的。
var s = "<h1>title</h1><p>text</p>";
var r = /((<\/?\w+>).*(<\/?\w+>))/g;
var a = s.match(r); //返回数组["<h1>title</h1>","<p>text</p>"]
反向引用在开发中主要有以下几种常规用法。
示例2
在正则表达式对象的 test() 方法中,以及字符串对象的 match() 和 search() 等方法中使用。在这些方法中,反向引用的值可以从 RegExp() 构造函数中获得。
var s = "abcdefghijklmn";
var r = /(\w)(\w)(\w)/;
r.test(s);
console.log(RegExp.$1); //返回第1个子表达式匹配的字符a
console.log(RegExp.$2); //返回第2个子表达式匹配的字符b
console.log(RegExp.$3); //返回第3个子表达式匹配的字符c
通过上面示例可以看到,正则表达式执行匹配检测后,所有子表达式匹配的文本都被分组存储在 RegExp() 构造函数的属性内,通过前缀符号$
与正则表达式中子表达式的编号来引用这些临时属性。其中属性 $1 标识符指向第 1 个值引用,属性 $2 标识符指向第 2 个值引用。
示例3
可以直接在定义的字符模式中包含反向引用。这可以通过使用特殊转义序列(如 \1、\2 等)来实现。
var s = "abcbcacba";
var r = /(\w)(\w)(\w)\2\3\1\3\2\1/;
var b = r.test(s); //验证正则表达式是否匹配该字符串
console.log(b); //返回true
在上面示例的正则表达式中,“\1”表示对第 1 个反向引用 (\w) 所匹配的字符 a 进行引用,“\2”表示对第 2 个反向引用 (\w) 所匹配的字符串 b 进行引用,“\3”表示对第 3 个反向引用 (\w) 所匹配的字符 c 进行引用。
示例4
可以在字符串对象的 replace() 方法中使用。通过使用特殊字符序列$1、$2、$3 等来实现。例如,在下面的示例中将颠倒相邻字母和数字的位置。
var s = "aa11bb22c3d4e5f6";
var r = /(\w+?)(\d+)/g;
var b = s.replace(r,"$2$1");
console.log(b); //返回字符串“aa11bb22c3 d4e5f6”
在上面例子中,正则表达式包括两个分组,第 1 个分组匹配任意连续的字母,第 2 个分组匹配任意连续的数字。在 replace() 方法的第 2 个参数中,$1 表示对正则表达式中第 1 个子表达式匹配文本的引用,而 $2 表示对正则表达式中第 2 个子表达式匹配文本的引用,通过颠倒 $1 和 $2 标识符的位置,即可实现字符串的颠倒来替换原字符串。
4.9 禁止引用
反向引用会占用一定的系统资源,在较长的正则表达式中,反向引用会降低匹配速度。如果分组仅仅是为了方便操作,可以禁止反向引用。
实现方法:在左括号的后面加上一个问号和冒号。
var s1 = "abc";
var r = /(?:\w*?)|(?:\d*?)/;
var a = r.test(si);
非引用型分组必须使用子表达式,但是又不希望存储无用的匹配信息,或者希望提高匹配速度来说,是非常重用的方法。
还没有评论,来说两句吧...