首页 > 解决方案 > 为什么正则表达式引擎允许/自动尝试在输入字符串的末尾进行匹配?

问题描述

注意:
* Python 用于说明行为,但这个问题与语言无关。
* 出于讨论的目的,仅假设单行输入,因为换行符(多行输入)的存在会导致手头问题的行为发生变化,$并且.这些变化是附带问题的。

大多数正则表达式引擎:

或许不用说,只有当所讨论的正则表达式匹配空字符串(并且正则表达式默认 / 配置为报告零长度匹配)时,这种匹配尝试才会成功。

这些行为至少乍一看是违反直觉的,我想知道是否有人可以为它们提供设计理由,尤其是因为:


请注意,正则表达式引擎的行为在零长度(空字符串)匹配后继续匹配的位置上有所不同。

任何一种选择(从相同的字符位置开始而不是从下一个开始)都是合理的 - 请参阅www.regular-expressions.info 中关于零长度匹配的章节

相比之下,.*$这里讨论的情况不同,对于任何非空输入,第一个匹配.*$不是零长度匹配,因此行为差异不适用-相反,字符位置应该在第一个之后无条件地前进匹配,如果你已经在最后,这当然是不可能的。
再一次,令我惊讶的是,尽管从定义上说什么都没有,但仍然尝试了另一场比赛。


[1] 我在$这里用作输入结束标记,即使在某些引擎(例如 .NET)中,它也可以标记输入结尾(可选地后跟换行符)。但是,当您使用无条件输入结束标记时,该行为同样适用\z

[2] Python 2.x 和 3.x 到 3.6.x 在这种情况下看似特殊的替换行为: python -c "import re; print(re.sub('.*$', '[\g<0>]', 'a'))"用于仅产生[a]- 即只找到并替换了一个匹配项。
从 Python 3.7 开始,这种行为现在就像在大多数其他正则表达式引擎中一样,其中执行了两次替换,产生[a][].

[3] 您可以通过 (a) 选择一种替换方法来避免该问题,该替换方法旨在最多找到一个匹配项,或者 (b) 用于^.*防止通过输入起始锚定找到多个匹配项。
(a) 可能不是一种选择,这取决于给定语言如何呈现功能;例如,PowerShell 的-replace操作符总是替换所有出现的;考虑以下尝试将所有数组元素包含在"...":中
'a', 'b' -replace '.*', '"$&"'。由于匹配两次,这会产生元素"a""""b""";
选项 (b), 'a', 'b' -replace '^.*', '"$&"', 解决了这个问题。

标签: regexlanguage-agnostic

解决方案


我给出这个答案只是为了演示为什么正则表达式希望允许任何代码出现在$模式中的最终锚点之后。假设我们需要创建一个正则表达式来匹配具有以下规则的字符串:

  • 以三个数字开头
  • 后跟一个或多个字母、数字、连字符或下划线
  • 仅以字母和数字结尾

我们可以写出以下模式:

^\d{3}[A-Za-z0-9\-_]*[A-Za-z0-9]$

但这有点笨重,因为我们必须使用彼此相邻的两个相似的字符类。相反,我们可以将模式写成:

^\d{3}[A-Za-z0-9\-_]+$(?<!_|-)

或者

^\d{3}[A-Za-z0-9\-_]+(?<!_|-)$

在这里,我们消除了其中一个字符类,而是在锚点$使用否定的lookbehind来断言最终字符不是下划线或连字符。

除了向后看之外,对我来说,为什么正则表达式引擎会允许在$锚点之后出现某些东西是没有意义的。我的观点是,正则表达式引擎可能允许后向出现在 . 之后$,并且在某些情况下这样做在逻辑上是有意义的。


推荐阅读