快速开始
教程
工具和语言
示例
参考
书评
正则表达式教程
引言
目录
特殊字符
不可打印字符
正则表达式引擎内部
字符类
字符类减法
字符类交集
简写字符类
锚点
单词边界
交替
可选项
重复
分组和捕获
反向引用
反向引用,第 2 部分
命名组
相对反向引用
分支重置组
自由间距和注释
Unicode
模式修饰符
原子分组
占有量词
前瞻和后顾
环视,第 2 部分
保持文本与匹配无关
条件
平衡组
递归
子例程
无限递归
递归和量词
递归和捕获
递归和反向引用
递归和回溯
POSIX 方括号表达式
零长度匹配
继续匹配
更多内容
引言
正则表达式快速入门
正则表达式教程
替换字符串教程
应用程序和语言
正则表达式示例
正则表达式参考
替换字符串参考
书评
可打印 PDF
关于此网站
RSS 源和博客
RegexBuddy—Better than a regular expression tutorial!

保持文本与迄今为止的整体正则表达式匹配

后顾通常用于匹配某些文本,这些文本前面有其他文本,而不将其他文本包含在整体正则表达式匹配中。 (?<=h)d 仅匹配 adhd 中的第二个 d。虽然很多正则表达式风格都支持后顾,但大多数正则表达式风格只允许在后顾中使用正则表达式语法的一个子集。 PerlBoost 要求后顾为固定长度。 PCRERuby 允许不同长度的 交替,但仍然不允许 量词,除了固定长度 {n}

为了克服后向查找的限制,Perl 5.10、PCRE 7.2、Ruby 2.0 和 Boost 1.42 引入了一项新功能,可用于代替后向查找,以实现其最常见的用途。 \K 保留到目前为止匹配的文本,使其不属于整个正则表达式匹配的一部分。 h\Kd 仅匹配 adhd 中的第二个 d

JGsoft 版本 一直支持不受限制的 后向查找,其灵活性远高于 \K。不过,JGsoft V2 还是添加了对 \K 的支持,以满足您的工作偏好。

了解正则表达式引擎内部机制

我们来看看 h\Kd 的工作原理。引擎从字符串的开头开始匹配尝试。 h 无法匹配 a。没有其他备选方案可供尝试。从字符串开头开始的匹配尝试失败。

引擎在字符串中向前移动一个字符,然后再次尝试匹配。 h 无法匹配 d

再次向前移动,h 匹配 h。引擎在正则表达式中向前移动。现在,正则表达式已到达正则表达式中的 \K,以及字符串中 h 和第二个 d 之间的位置。 \K 唯一的作用是,如果此匹配尝试最终成功,正则表达式引擎应假装匹配尝试从 hd 之间当前位置开始,而不是从真正开始的第一个 dh 之间开始。

引擎在正则表达式中向前移动。 d 匹配字符串中的第二个 d。找到一个整体匹配。由于 \K 保存的位置,字符串中的第二个 d 被返回为整体匹配。

\K 仅影响成功匹配后返回的位置。它不会在匹配过程中移动匹配尝试的开始位置。正则表达式 hhh\Kd 匹配 hhhhd 中的 d。此正则表达式首先从字符串开头匹配 hhh。然后,\K 记录字符串中 hhhhd 之间的位置。然后,d 无法匹配字符串中的第四个 h。从字符串开头开始的匹配尝试失败。

现在,引擎必须在字符串中向前移动一个字符,然后再开始下一次匹配尝试。它从匹配尝试的实际开始位置向前移动,该位置在字符串开头。由 \K 存储的位置不会改变这一点。因此,第二次匹配尝试从字符串中第一个 h 后的位置开始。从那里开始,hhh 匹配 hhh\K 记录位置,d 匹配 d。现在,考虑由 \K 记住的位置,d 被返回为整体匹配。

\K 可以在任何地方使用

你几乎可以在任何正则表达式的任何地方使用 \K。你应该避免在后向引用中使用它。你可以在组中使用它,即使它们有量词。你可以在正则表达式中使用任意多个 \K 实例。当 ab 前置时,(ab\Kc|d\Ke)f 匹配 cf。当 d 前置时,它也匹配 ef

\K 不影响捕获组。当 (ab\Kc|d\Ke)f 匹配 cf 时,捕获组捕获 abc,就好像 \K 不存在一样。当正则表达式匹配 ef 时,捕获组存储 de

\K 的限制

由于 \K 不影响正则表达式引擎执行匹配过程的方式,因此它比 Perl、PCRE 和 Ruby 中的后向引用提供了更大的灵活性。你可以在 \K 的左侧放置任何内容,但你受限于在后向引用中可以放置的内容。

但这种灵活性是有代价的。后向引用确实会向后遍历字符串。这允许后向引用在匹配尝试开始之前检查匹配。当匹配尝试在上次匹配的末尾开始时,后向引用可以匹配上次匹配的一部分文本。而 \K 无法做到这一点,因为它不影响正则表达式引擎执行匹配过程的方式。

如果你遍历字符串 aaaa(?<=a)a 的所有匹配项,你将获得三个匹配项:字符串中的第二个、第三个和第四个 a。第一个匹配尝试从字符串的开头开始,并因后向引用失败而失败。第二个匹配尝试从第一个和第二个 a 之间开始,后向引用在此成功,第二个 a 匹配。第三个匹配尝试从刚刚匹配的第二个 a 之后开始。后向引用在此也成功。前一个 a 是上次匹配的一部分并不重要。因此,第三个匹配尝试匹配第三个 a。同样,第四个匹配尝试匹配第四个 a。第五个匹配尝试从字符串的末尾开始。后向引用仍然成功,但没有字符供 a 匹配。匹配尝试失败。引擎已到达字符串的末尾,迭代停止。五次匹配尝试找到了三个匹配项。

当你迭代字符串 aaaa 中的 a\Ka 时,情况有所不同。你只会得到两个匹配:第二个和第四个 a。第一次匹配尝试从字符串的开头开始。正则表达式中的第一个 a 匹配字符串中的第一个 a\K 记录位置。第二个 a 匹配字符串中的第二个 a,该 a 作为第一个匹配项返回。第二次匹配尝试从刚刚匹配的第二个 a 之后开始。正则表达式中的第一个 a 匹配字符串中的第三个 a\K 记录位置。第二个 a 匹配字符串中的第四个 a,该 a 作为第一个匹配项返回。第三次匹配尝试从字符串的末尾开始。a 失败。引擎已到达字符串的末尾,迭代停止。三次匹配尝试找到了两个匹配项。

基本上,当 \K 之前的正则表达式部分可以匹配与 \K 之后的正则表达式部分相同的文本时,你就会遇到这个问题。如果这些部分不能匹配相同的文本,那么使用 \K 的正则表达式将找到与使用后向引用重写的相同正则表达式相同的匹配项。在这种情况下,你应该使用 \K 而不是后向引用,因为这将在 Perl、PCRE 和 Ruby 中为你提供更好的性能。

另一个限制是,虽然反向引用有正向和负向变体,但 \K 不提供任何否定方式。 (?<!a)b 完全匹配字符串 b,因为它是一个没有前导“a”的“b”。 [^a]\Kb 完全不匹配字符串 b。尝试匹配时,[^a] 匹配 b。现在正则表达式已到达字符串的末尾。 \K 记录此位置。但现在 b 已无内容可匹配。匹配尝试失败。 [^a]\Kb(?<=[^a])b 相同,而两者都与 (?<!a)b 不同。