正则表达式高级功能之断言 郝伟 2022/08/23 [TOC]

1. 1 问题描述

正则表达式的基本功能能够满足大部分情况下的需求,但是有一些需求在满足时比较困难。举例来说,有以下文章(选自《新概念英语第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当然,我们可以编写程序来处理这种情况,但是实际上使用正则是可以解决一。所以,本文介绍一种正则表达式的高级功能:向前或向后断言功能。

2. 1.1 正则中的断言

在以上的问题中,实际上就是对以 . 为结尾的字符串进行断言,如果是以 MrMrs 开头的 . 号则不选择。在正则表达式中,提供了断言(Assertions)用于处理相应的功能。

3. 1.2 断言的分类

断言主要有以下四种

graph LR
A[正则中的断言<BR>Assertions in Regex]
B[向前断言<br>Lookahead Assertions]
B1[向前肯定断言<br>Possitive Lookahead Assertions]
B2[向前否定断言<br>Negative Lookahead Assertions]
C[向后断言<br>Lookbehind Assertions]
C1[向后肯定断言<br>Possitive Lookbehind Assertions]
C2[向后否定断言<br>Negative Lookbehind Assertions]

A-->B
B-->B1
B-->B2
A-->C
C-->C1
C-->C2

Mr.Mrs. 中的 . 的判断,就是向后断言种的一种,下面我们就断言的内容进行展开介绍。

3.1. 1.3 语法汇总

  • 正则中的断言 (Assertions in Regex)
    • 向前断言 (Lookahead Assertions)
      • 向前肯定断言 (Possitive Lookahead Assertions) match(?=pattern)
      • 向前否定断言 (Negative Lookahead Assertions) match(?!=pattern)
    • 向后断言 (Lookbehind Assertions)
      • 向后肯定断言 (Possitive Lookbehind Assertions) (?<=pattern)match
      • 向后否定断言 (Negative Lookbehind Assertions) (?<!=pattern)match

其中 match 是用于匹配内容的正则表达式,而 patten 是断言中的正则表达式。

4. 2 向前断言(lookahead assertions)

向前断言分为 向前肯定断言(look ahead positive assert)和 向前否定断言(look ahead negtive assert)两种情况。

4.1. 2.1 向前肯定断言 (?=pattern)

向前断言是指断言写在正则的后面,向前进行断言查询。

4.1.1. 2.2.1 定义

向前肯定断言(look ahead positive assert)的格式为 match(?=pattern),其中 match 为需要进行匹配的正则表达式,而 pattern 为需要断言正则表达式,如 (?=\w{1,3})用于匹配长度为1-3位的单词。 该匹配不需要获取供以后使用。例如,Windows(?=95|98|NT|2000) 能匹配 Windows2000 中的 Windows,但不能匹配 Windows3.1 中的 Windows

4.1.2. 2.2.2 注意事项

断言可以理解为选择的条件,所以:

  1. 断言不会被选择到,在上面的示例中只返回 Windows 而不会返回 Windows95
  2. 断言不消耗字符,即在一个匹配发生后,下一次匹配会紧接 Windows 之后开始,而不是 Windows95 之后开始。

所以,向前断言的工作原理是这样:

  1. 首先,使用原正则进行匹配,得到一个输出列表L;
  2. 然后,使用向前断言对L中的匹配再进行筛选,满足条件的为最终结果。

让我们继续来看这样的示例,现在将表达式修改为:(?=(.+)t)\w+t\b,则输出为:

4.1.3. 2.2.3 更多示例

示例文本如下:

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, 35012,345

4.2. 2.2 向前否定断言 (?!pattern)

类似的,我们可以有向前否定断言 (look ahead negtive asset),即不满足指定断言的匹配,语法为 (?!pattern)。对于上面的示例,我们使用以下正则模式 (?=(\w{2,3})t)\b\w+t\b,可以得到以下结果。 CSDN的图 显然,结果是对向前肯定断言的结果集合的否定结果集。

5. 3 向后断言(look behind assert)

与正面断言对应,正则表达式还定义了向后断言,分为向后肯定断言(look ahead positive assert)和向后否定断言(look ahead negtive assert)。

5.1. 3.1 向后肯定断言 (?<=pattern)

向后肯定断言(look behind possitive asset)是向后进行判断的,语法格式为 (?<=pattern)。让我们来看看本文开头提出的问题,即若有 MrMrs 开头的 . 号如何判断。使用 \.(?<=(Mr|Mrs)\.) 进行向后肯定断言后,可以匹配到所有 MrMrs 开头的 . 号,如下图所示。 CSDN的图

5.2. 3.2 向后否定断言 (?<!pattern)

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

6. 4 扩展验证

现在我们知道了断言的使用方法,那么有这样一个问题,如果只写断言会发生什么,现在进行测试。

6.1. 4.1 扩展示例1

仍然是对之前的文档,现在匹配 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中间的位置,即从这里开始左侧的内容都满足条件。

6.2. 4.2 扩展示例2

再看这样的示例:从上面的文本中选择以 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

7. 5 小结

本文介绍了一种更加强大正则表达式的语法,能够对匹配目标的前后进行条件断言,从而实现更加强大的断言匹配功能。通过这些语法的使用,我们可以实现更加强大的匹配功能。 最后,再补充两种带有问号的表达式,可以进一步提高匹配功能,有兴趣的读者可以进一步研究。

  • (pattern)
    匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '(' 或 ')'。
  • (?:pattern)
    匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, industr(?:y|ies) 显然比 industry|industries 的表达方式更加精练。

8. 6 参考文献

[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

9. 7 更新历史

2022/08/23 添加了一些图片,对文字进行了校对,示例的错误进行了修正 2022/08/22 内容大幅优化更新,修改了原先几个错误的理解,并添加了更多示例 2020/12/08 首发本文。

results matching ""

    No results matching ""