ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

正则表达式——重复匹配

2026/8/5 1:08:45 拓冰建站 浏览量
正则表达式——重复匹配 重复匹配1、有多少个匹配1.1、匹配一个或多个字符1.2、匹配零个或多个字符1.3、匹配零个或一个字符2、匹配的重复次数2.1、为重复匹配次数设定一个精确的值2.2、为重复匹配次数设定一个区间2.3、匹配“至少重复多少次”3、防止过度匹配1、有多少个匹配通过前面的学习我们已经把正则表达式模式匹配操作的基础知识全都介绍给了大家但我们给出的每个例子都有一个非常严格的限制。现在请大家思考一下如何构造一个匹配电子邮件地址的正则表达式。电子邮件地址的基本格式应该是如下所示的样子texttext.text利用前一章讨论的元字符你可能会写出一个如下所示的正则表达式\w\w\.\w\w可以匹配所有的字母和数字字符以及下划线字符_这个字符在电子邮件地址里是合法的​字符不需要被转义但字符需要。这个正则表达式本身没有任何错误可它几乎没有任何实际的用处——它只能匹配ab.c形式的电子邮件地址虽然在语法方面没有任何问题但这显然不是一个合法的地址​。导致这一结果的关键是\w只能匹配单个字符而我们无法预知电子邮件地址的各个字段会有多少个字符。举个最简单的例子下面这些都是合法的电子邮件地址但它们在前面的字符个数都不一样。bforta.com benforta.com bfortaforta.com要想解决这类问题我们需要一种能够匹配多个字符的办法这可以通过使用几种特殊的元字符来做到。1.1、匹配一个或多个字符要想匹配同一个字符或字符集合的多次重复只要简单地给这个字符或字符集合加上一个字符作为后缀就行了。匹配一个或多个字符至少一个不匹配零个字符的情况​。比如a匹配a本身a将匹配一个或多个连续出现的a。类似地​[0-9]匹配任意单个数字​[0-9]将匹配一个或多个连续的数字。在给一个字符集合加上后缀的时候必须把放在这个字符集合的外面。比如说​[0-9]是正确的​[0-9]则不是。​[0-9]其实也是一个合法的正则表达式但它匹配的不是一个或多个数字它定义了一个由数字0到9和构成的字符集合因而只能匹配一个单个的数字字符或加号。虽然合法可它并不是我们需要的东西。重新回到电子邮件地址的例子我们这次将使用来匹配一个或多个字符文本Send personal email to benforta.com. For questions about a book use supportforta.com. Feel free to send unsolicited email to spamforta.com (wouldnt it be nice if it were that simple, huh?).正则表达式\w\w\.\w结果这个模式把原始文本里的3个电子邮件地址全都正确地匹配出来了。这个正则表达式先用第一个\w匹配一个或多个字母数字字符再用第二个\w匹配后面的一个或多个字符然后匹配一个.字符使用转义序列\.​最后用第三个\w匹配电子邮件地址的剩余部分。还可以用来匹配一个或多个字符集合。为了演示这种用法我们在下面这个例子里使用了和刚才一样的正则表达式但原始文本和上一个例子稍有不同文本Send personal email to benforta.com. or ben.fortaforta.com. For questions about a book use supportforta.com. If your message is urgent try benurgent.forta.com. Feel free to send unsolicited email to spamforta.com (wouldnt it be nice if it were that simple, huh?).正则表达式\w\w\.\w结果这个正则表达式匹配到了5个电子邮件地址但其中有两个不够完整。为什么会这样因为我们在构造这个正则表达式的时候只想到在字符的后面会有一个.字符分开两个字符串的情况没有想到在字符的前面还会有字符。因此虽然ben.fortaforta.com是一个完全合法的电子邮件地址但这个正则表达式只能匹配forta而不是ben.forta——别忘了\w只能匹配字母和数字字符不能匹配出现在字符串中间的.字符。要想干净彻底地解决这个问题我们需要匹配\w或.。用正则表达式的术语来说我们需要匹配字符集合[\w\.]​。下面是上面那个例子的改进版本正则表达式[\w.\[\w.]\.\w结果问题似乎得到了圆满解决。​[\w.]将匹配字符集合[\w.]​字母数字字符、下划线和的一次或多次重复出现ben.forta完全符合这一条件。考虑到有些电子邮件地址会有多层域名或主机名​我们在字符的后面也使用了一个[\w.]。我们没有对字符集合[\w.]里的字符进行转义。尽管如此它还是把原始文本里的.字符匹配出来了。一般来说当在字符集合里使用的时候像.和这样的元字符将被解释为普通字符不需要被转义——但转义了也没有坏处。​[\w.]的使用效果与[\w\ .]是一样的。1.2、匹配零个或多个字符匹配一个或多个字符但不匹配零个字符——最少也要匹配一个字符。那么如果你想匹配一个可有可无的字符——也就是该字符可以出现零次或多次的情况你该怎么办呢这种匹配需要用*元字符来完成。*的用法与完全一样——只要把它放在一个字符或一个字符集合的后面就可以匹配该字符或字符集合连续出现零次或多次的情况。比如说模式B.* Forta将匹配B Forta、B.Forta、Ben Forta和其他有类似规律的组合。为了演示和*的区别我们来看两个匹配电子邮件地址的例子。先看第一个文本Hello .benforta.com is my wmail address.正则表达式[\w.][\w.]\.\w结果[\w.]将匹配字符集合[\w.]​字母数字字符、下划线和的一次或多次重复出现而.ben。完全符合这一条件。这显然是一个打字错误原始文本里多了一个​但这并不是我们这里最关心的问题。问题的关键在于虽然是电子邮件地址里的合法字符但把它用作电子邮件地址的第一个字符就不合法了。一个电子邮件地址可以有任意多个字符但它的第一个字符必须是一个字母或数字字符。根据这一要求我们真正需要的是一个如下例所示的模式文本Hello .benforta.com is my wmail address.正则表达式\w[\w.]*[\w.]\.\w结果这个模式看起来相当复杂但并不难理解。开头的\w负责匹配电子邮件地址里的第一个字符一个字母数字字符不包括.字符​。接下来的[\w.]*负责匹配电子邮件地址里第一个字符之后、字符之前的所有字符——这个部分可以包含零个或多个字母数字字符和字符。在这个例子里解决问题的关键是能不能想到用[\w.]*来匹配字符集合[\w.]​字母数字字符、下划线和的零次或多次重复出现。1.3、匹配零个或一个字符另一个非常有用的元字符是?​。​?只能匹配一个字符或字符集合的零次或一次出现最多不超过一次——请仔细体会?与和*的相似和区别之处。如果需要在一段文本里匹配某个特定的字符或字符集合而该字符可能出现、也可能不出现​?无疑是最佳的选择。请看下面这个例子文本Hhe URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instead.正则表达式http:\/\/[\w.]\/结果这个模式只匹配到了第一个URL地址以http://开头的那个​没能匹配到第二个以https://开头的那个​。简单地在http的后面加上一个s*s的零次或多次重复并不能真正解决这个问题因为那会使得httpsssss://如此开头的URL地址显然是不合法的也被认为是一个合法的匹配。怎么办看看下面这个例子就知道了——在http的后面加上一个s? 正则表达式https?:\/\/[\w.]\/结果这个模式的开头部分是https?。​在这里的含义是我前面的字符s要么不出现要么最多出现一次。换句话说https?/既可以匹配http://也可以匹配https://但也就仅此而已。2、匹配的重复次数正则表达式里的、*和解决了许多问题但有些问题光靠它们还不够。请思考以下问题和*匹配的字符个数没有上限。我们无法为它们将匹配的字符个数设定一个最大值。、*和至少匹配零个或一个字符。我们无法为它们将匹配的字符个数另行设定一个最小值。如果只使用和*我们无法把它们将匹配的字符个数设定为一个精确的数字。为了解决这些问题并让程序员对重复性匹配有更多的控制正则表达式语言提供了一个用来设定重复次数interval的语法。重复次数要用{和}字符来给出——把数值写在它们之间。注意 {和}是元字符。如果需要匹配{和}本身就应该用\对它们进行转义。不过即使你没有对{和}进行转义大部分正则表达式实现也能正确地处理它们根据具体情况把它们解释为普通字符或元字符​。话虽如此为了避免不必要的麻烦你最好不要依赖这种行为在需要把{和}当做普通字符来匹配的场合还是使用它们的转义序列{和}比较稳妥。2.1、为重复匹配次数设定一个精确的值如果你想为重复匹配次数设定一个精确的值把那个数字写在{和}之间即可。比如说{3}意味着模式里的前一个字符或字符集合必须在原始文本里连续重复出现3次才算是一个匹配如果只重复了两次则不算是一个匹配。为了演示这种用法我们再来看一下匹配RGB值的例子​。你应该记得RGB值是一个十六进制数值这个值分成3个部分每个部分包括两位十六数字。#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]下面是我们在前面用来匹配RGB值的模式它使用了POSIX字符类#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]这两个模式本身并无不妥但美中不足的是你不得不重复写出6次相同的字符集合或POSIX字符类​。下面是一个同样的例子但我们这次将使用{ }语法来明确指定一个重复次数文本BODY BGCOLOR#336633 TEXT#FFFFFF MARGINWIDTH0 MARGINHEIGHT0 TOPMARGIN0 LEFTMARGIN0正则表达式#[[:xdigit:]]{6}结果[:xdigit]匹配一个十六进制数字{6}要求这个POSIX字符类必须连续出现6次。类似地使用模式#[0-9A-Fa-f]{6}也可以解决这个问题。2.2、为重复匹配次数设定一个区间{}语法还可以用来为重复匹配次数设定一个区间——也就是为重复匹配次数设定一个最小值和一个最大值。这种区间必须以{2, 4}这样的形式给出——{2, 4}的含义是最少重复2次、最多重复4次。在下面的例子里我们将使用一个这样的正则表达式来检查日期的格式文本4/8/03 10-6-2004 2/2/2 01-01-01正则表达式\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4}结果注意上面这个例子里的模式并不能检查日期值是否有效诸如54/67/9999之类的无效日期也能通过这一测试。它只能用来检查日期值的格式是否正确这一环节通常安排在日期值本身的有效性检查之前​。注意 重复次数可以是0。比如{0, 3}表示重复次数可以是0、1、2或3。2.3、匹配“至少重复多少次”{ }语法的最后一种用法是给出一个最小的重复次数但不必给出一个最大值​。{ }的这种用法与我们用来为重复匹配次数设定一个区间的{ }语法很相似只是省略了最大值部分而已。比如说{3, }表示至少重复3次与之等价的说法是“必须重复3次或更多次”​。我们来看一个综合了本章主要内容的例子。在这个例子里我们使用一个正则表达式把所有大于或等于$100美元的金额找出来文本1001: $496.80 1002: $1290.69 1003: $26.43 1004: $613.42 1005: $7.61 1006: $414.90 1007: $25.00正则表达式\d: \$\d{3,}\.\d{2}结果这个例子里的原始文本来自一份报表它的第一列是定单号第二列是定单金额。我们构造的正则表达式首先使用了一个\d来匹配定单号这部分其实可以省略——我们可以只匹配金额部分而不是匹配包括定单号在内的一整行​。模式\$\d{3, }\.\d{2}用来匹配金额部分\$匹配$、\d{3, }匹配至少3位数字也就是所有大于或等于$100美元的金额​、\.匹配.、\d{2}匹配小数点后面的两位数字。整个模式从7条记录里正确地匹配到了4条符合要求的记录。3、防止过度匹配只能匹配零个或一个字符{n}和{m, n}也有一个重复次数的上限换句话说这几种语法所定义的“重复次数”都是有限的。但本章介绍的其他重复匹配语法在重复次数方面都没有上限值而这样做有时会导致过度匹配的现象。到目前为止我们选用的例子都不存在过度匹配的问题但你迟早会遇到类似于下面这个例子的情况。这个例子里的原始文本来自一个Web页面其中包含着两个HTMLB标签而我们的任务是用一个正则表达式把那两个B标签里的文本匹配出来为了对这些文本进行替换或排版等​。下面就是这个例子文本This offer is not available to customers living in BAK/B and BHI/B.正则表达式[Bb].*\/[Bb]结果[Bb]匹配B标签大小写均可,/[Bb]匹配/B标签也是大小写均可​。但这个模式只找到了一个匹配而不是预期中的两个第一个B标签之后、最后一个/B标签之前的所有东西——AK/B and BHI——被*一网打尽。虽然没有漏掉我们想要匹配的文本但问题是第2个B标签不明不白地“失踪”了。为什么会这样因为*和都是所谓的“贪婪型”元字符它们在进行匹配时的行为模式是多多益善而不是适可而止的。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾而不是从这段文本的开头匹配到碰到第一个匹配时为止。在不需要这种“贪婪行为”的时候该怎么办答案是使用这些元字符的“懒惰型”版本​“懒惰”在这里的含义是匹配尽可能少的字符——与“贪婪型”元字符的行为模式刚好相反​。懒惰型元字符的写法很简单只要给贪婪型元字符加上一个后缀即可。下表列出了几个常用的贪婪型元字符和它们的懒惰型版本。*?是*的懒惰型版本下面是使用*?来解决刚才那个例子的做法正则表达式[Bb].*?\/[Bb]结果问题得到了圆满解决。因为使用了懒惰的*?第一个匹配将仅限于AK原始文本里的BHI/B成为了第二个匹配。