本节书摘来自异步社区《正则表达式经典实例(第2版)》一书中的第2章,第2.12节,作者: 【美】Jan Goyvaerts , Steven Levithan著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.12 把正则表达式的一部分重复多次
问题描述
创建正则表达式来匹配下列种类的数字。
- 一个googol(一个100位的十进制整数)。
- 一个32位的十六进制整数。
- 一个32位的十六进制整数,带有一个可选的h后缀。
- 一个浮点数,包含可选的整数部分、必需的小数部分和可选的指数部分。每个部分都允许任意多个数字。
解决方案
Googol
\b\d{100}\b
正则选项:无
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
十六进制整数
\b[a-f0-9]{1,8}\b
正则选项:不区分大小写
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
带可选后缀的十六进制整数
\b[a-f0-9]{1,8}h?\b
正则选项:不区分大小写
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
浮点数
\d*\.\d+(e\d+)?
正则选项:不区分大小写
正则流派:.NET、Java、JavaScript、PCRE、Perl、Python、Ruby
讨论
固定次数重复
量词‹{n}›用来重复之前的正则表达式记号n次,其中n是一个正整数。在‹bd{100}b›中的‹d{100}›会匹配一个包含100个数字的字符串。你也可以通过把‹d›敲100遍来达到同样的效果。
‹{1}›重复之前的记号一次,这样和没有任何量词是等价的。‹ab{1}c›与‹abc›是相同的正则表达式。
‹{0}›重复之前的记号0次,实际上也就是把它从正则表达式中删除。‹ab{0}c›与‹ac›是相同的正则表达式。
可变次数重复
对于可变次数重复,我们使用量词‹{n,m}›,其中n是一个正整数,并且m大于n。‹b[a-f0-9]{1,8}b›匹配一个包含1~8个数字的十六进制整数。对于可变次数重复,尝试重复的顺序就变得至关重要。这会在实例2.13中详细讲解。
如果n和m是相等的,那么我们就会得到固定次数的重复。‹bd{100,100}b›与‹bd{100}b›是相同的正则表达式。
无限次数重复
量词‹{n,}›支持无限次数重复,其中n是一个正整数。实际上,无限次数重复是一个没有上限的可变次数重复。
‹d{1,}›匹配至少一个数字,‹d+›也一样。在一个不是量词的正则表达式记号之后添加一个加号,意味着“一次或多次”。实例2.13会讲解在量词之后跟加号的含义。
‹d{0,}›匹配至少0个数字,‹d*›也一样。星号永远都意味着“0次或多次”。除了支持无限次数重复之外,‹{0,}›和星号还把之前的记号变成了可选的。
设置可选内容
如果我们使用可变次数重复,并把n设置为0,事实上我们就是把在该量词之前的记号变成了可选的。‹h{0,1}›匹配一次‹h›或者根本不存在。如果不存在h,那么‹h{0,1}›会得到一个长度为0的匹配。如果你使用‹h{0,1}›来作为一个正则表达式,那么它会在目标文本中每个不是h的字符之前找到一个长度为0的匹配。每个h则会成功匹配一个字符(也就是h)。
‹h?›与‹h(0,1)›的效果是一样的。在一个合法和完整的非量词正则表达式记号之后的问号意味着“0或1次”。下一个实例会解释在量词之后的问号的含义。
在一个起始括号之后使用问号或者任意其他量词,都是一个语法错误。Perl和沿用它的流派利用它为正则语法添加“Perl扩展”。前面的实例讲解了非捕获分组和命名捕获分组,它们都使用了在起始括号之后的问号来作为其语法的一部分。这些问号根本不是量词,它们只是属于非捕获分组和命名捕获分组的语法中的一部分。随后的实例中会讲解使用‹(?›语法的更多不同用途的分组。
重复分组
如果在分组的结束括号之后放一个量词的话,那么整个分组就会重复。‹(?:abc){3}›与‹abcabcabc›是相同的。
量词还可以进行嵌套。‹(ed+)?›匹配一个e之后跟着至少一个数字,或者是一个长度为0的匹配。在我们的浮点数的正则表达式中,这是可选的指数部分。
捕获分组也可以重复。在实例2.9中解释过,分组匹配在每次引擎退出该分组的时候捕获文本,并会覆盖该分组在之前匹配的任何文本。‹(dd){1,3}›会匹配一个包含2个、4个或6个数字的字符串。引擎会退出该分组3次。当这个正则表达式匹配到123456的时候,捕获分组中保存的是56,因为该分组的最后一次重复存储的是56。另外两次分组匹配的文本,也就是12和34,是无法获取的。
‹(dd){3}›会与‹dddd(dd)›捕获相同的文本。如果想要用捕获分组来捕获所有2个、4个或6个字符,而不只是最后2个,就必须用捕获分组包围量词,而不只是重复该捕获分组:‹((?:dd){1,3})›。这里我们用非捕获分组取代了捕获分组的分组功能。我们也可以使用两个捕获分组:‹((dd){1,3})›。在后一个正则表达式匹配123456的时候,‹1›中保存的是123456,而在‹2›中则保存了56。
只有.NET的正则表达式引擎才支持获取捕获分组的所有重复捕获的文本。如果直接查找该分组的Value属性,你会得到字符串56,就像所有其他正则表达式引擎一样。在正则表达式中的反向引用和替代文本也会代入56,但是如果使用分组的CaptureCollection,你就会得到一个栈,其中包含56、34和12。