正则表达式高级功能之断言
郝伟 2022/08/23
正则表达式的基本功能能够满足大部分情况下的需求,但是有一些需求在满足时比较困难。举例来说,有以下文章(选自《新概念英语第3册》第14课:
After her husband Mr. Richards has gone to work, Mrs. Richards sent her children to school and went upstairs to her bedroom. She was too excited to do any housework that morning, for in the evening she would be going to a fancy-dress party with her husband. She intended to dress up as a ghost and as she had made her costume the night before, she was impatient to try it on. Though the costume consisted only of a sheet, it was very effective. After putting it on, Mrs. Richards went downstairs. She wanted to find out whether it would be confortable to wear.
为了将句子提取出来,所以就选择句号标点作为分隔符进行拆分。我们可以中所有的句号,即.号,然后替换成 .\n 即可。但是,在执行的时候发现,在使用 . 进行匹配的时候,就会把 Mr. 和 Mrs. 中的 . 也一并匹配到。i当然,我们可以编写程序来处理这种情况,但是实际上使用正则是可以解决一。所以,本文介绍一种正则表达式的高级功能:向前或向后断言功能。
在以上的问题中,实际上就是对以 . 为结尾的字符串进行断言,如果是以 Mr 或 Mrs 开头的 . 号则不选择。在正则表达式中,提供了断言(Assertions)用于处理相应的功能。
断言主要有以下四种
对 Mr. 和 Mrs. 中的 . 的判断,就是向后断言种的一种,下面我们就断言的内容进行展开介绍。
match(?=pattern)match(?!=pattern)(?<=pattern)match(?<!=pattern)match其中 match 是用于匹配内容的正则表达式,而 patten 是断言中的正则表达式。
向前断言分为 向前肯定断言(look ahead positive assert)和 向前否定断言(look ahead negtive assert)两种情况。
向前断言是指断言写在正则的后面,向前进行断言查询。
向前肯定断言(look ahead positive assert)的格式为 match(?=pattern),其中 match 为需要进行匹配的正则表达式,而 pattern 为需要断言正则表达式,如 (?=\w{1,3})用于匹配长度为1-3位的单词。 该匹配不需要获取供以后使用。例如,Windows(?=95|98|NT|2000) 能匹配 Windows2000 中的 Windows,但不能匹配 Windows3.1 中的 Windows。
断言可以理解为选择的条件,所以:
Windows 而不会返回 Windows95Windows 之后开始,而不是 Windows95 之后开始。所以,向前断言的工作原理是这样:
让我们继续来看这样的示例,现在将表达式修改为:(?=(.+)t)\w+t\b,则输出为:
示例文本如下:
Windows 95
Windows 98
Windows 2000
Windows XP
USD 100
USD 350
USD 12,345
Windows(?= (95|98|NT|2000)) 匹配前3个 Windows,最后1个匹配不到。\b\w{2,3}(?=t) 匹配所有以t结尾的单词的的中间2-3个字符,如 light中的 igh(?<=USD )\d+,*\d+
100, 350 和 12,345。类似的,我们可以有向前否定断言 (look ahead negtive asset),即不满足指定断言的匹配,语法为 (?!pattern)。对于上面的示例,我们使用以下正则模式 (?=(\w{2,3})t)\b\w+t\b,可以得到以下结果。

显然,结果是对向前肯定断言的结果集合的否定结果集。
与正面断言对应,正则表达式还定义了向后断言,分为向后肯定断言(look ahead positive assert)和向后否定断言(look ahead negtive assert)。
向后肯定断言(look behind possitive asset)是向后进行判断的,语法格式为 (?<=pattern)。让我们来看看本文开头提出的问题,即若有 Mr 或 Mrs 开头的 . 号如何判断。使用 \.(?<=(Mr|Mrs)\.) 进行向后肯定断言后,可以匹配到所有 Mr 或 Mrs 开头的 . 号,如下图所示。

通过上面的示例,让我们看看最终解决问题的方案,即使用向后否定断言(look behind negtive asset),语法格式为 (?<!pattern)。编写正则模式 \.(?<!(Mr|Mrs)\.),则可以得到最终的结果,如下所示。

如图所示,我们截取到了所有不包括 Mr 或 Mrs 这样编写的点号。
现在我们知道了断言的使用方法,那么有这样一个问题,如果只写断言会发生什么,现在进行测试。
仍然是对之前的文档,现在匹配 c(?=(\w{5})t) 可以得到
图4-1 向前匹配示例
这个正则用于匹配c,它的后面跟有5个字母且第6个字母是t。如果我们把c去除,就会得到如下所示:
图4-2 仅向前正则匹配示例
如图4-2所示,会得到5个匹配,只要是满足有5个字母且第6个字母是t的都会匹配上。但是仔细观察,可以发现匹配的位置并不是字母的位置,如上面的 considered 或 confortable 都没有完全落在字母c上。这不是显示错误,而是表示选择的不是字母,而是位置。如 considered 落在 c和o中间的位置,即从这里开始左侧的内容都满足条件。
再看这样的示例:从上面的文本中选择以 t 结尾的单词,正则模式为 \b\w+t\b,效果如下所示。
图1 匹配效果图 CSDN的图1
现在我们提出这样的需求,只筛选长度为2和3的单词,正则模式为 (?=(\w{2,3})t)\b\w+t\b,这样我们就可以得到以下的筛选结果。
图2 加上向前判断的效果 CSDN的图2
现在我们来分析一下这个过程,新加入断言的正则表达式 (?=(\w{2,3})t)\b\w+t\b 实际上是由两部分组成:
(?=(\w{2,3})t) 新加入的前身断言,为方便描述记为 A;\b\w+t\b 原先的匹配正则,为方便描述记为 B。本文介绍了一种更加强大正则表达式的语法,能够对匹配目标的前后进行条件断言,从而实现更加强大的断言匹配功能。通过这些语法的使用,我们可以实现更加强大的匹配功能。
最后,再补充两种带有问号的表达式,可以进一步提高匹配功能,有兴趣的读者可以进一步研究。
(pattern) (?:pattern) industr(?:y|ies) 显然比 industry|industries 的表达方式更加精练。[1] 正则表达式在线测试工具:http://tools.haokh.net/Regex
[2] Runoob.com 正则表达式 - 元字符 https://www.runoob.com/regexp/regexp-metachar.html
[3] 正则表达式在线生成工具, http://tools.jb51.net/regex/create_reg
[4] Assertions in Regex https://www.geeksforgeeks.org/perl-assertions-in-regex/
[5] Regex Lookahead, https://regextutorial.org/positive-and-negative-lookahead-assertions.php
2022/08/23 添加了一些图片,对文字进行了校对,示例的错误进行了修正
2022/08/22 内容大幅优化更新,修改了原先几个错误的理解,并添加了更多示例
2020/12/08 首发本文。