regex - 为什么正则表达式引擎允许/自动尝试在输入字符串的末尾进行匹配?
问题描述
注意:
* Python 用于说明行为,但这个问题与语言无关。
* 出于讨论的目的,仅假设单行输入,因为换行符(多行输入)的存在会导致手头问题的行为发生变化,$
并且.
这些变化是附带问题的。
大多数正则表达式引擎:
接受在输入字符串[1]结束后显式尝试匹配表达式的正则表达式。
$ python -c "import re; print(re.findall('$.*', 'a'))" [''] # !! Matched the hypothetical empty string after the end of 'a'
在全局查找/替换时,即在查找给定正则表达式的所有非重叠匹配时,并且到达字符串的末尾时,意外地尝试再次匹配[2],如相关问题的答案中所述:
$ python -c "import re; print(re.findall('.*$', 'a'))" ['a', ''] # !! Matched both the full input AND the hypothetical empty string
或许不用说,只有当所讨论的正则表达式匹配空字符串(并且正则表达式默认 / 配置为报告零长度匹配)时,这种匹配尝试才会成功。
这些行为至少乍一看是违反直觉的,我想知道是否有人可以为它们提供设计理由,尤其是因为:
- 这种行为的好处并不明显。
.*
相反,在用和等模式全局查找/替换的情况下.*$
,这种行为是完全令人惊讶的。[3]- 更尖锐地问这个问题:为什么设计用于查找正则表达式的多个非重叠匹配的功能 - 即全局匹配 -如果它知道整个输入已经被消耗,甚至决定尝试另一个匹配,而不管是什么正则表达式是(尽管您永远不会看到带有至少不匹配空字符串的正则表达式的症状)
- 以下语言/引擎表现出令人惊讶的行为:.NET、Python(2.x 和 3.x)[2]、Perl(5.x 和 6.x)、Ruby、Node.js (JavaScript)
请注意,正则表达式引擎的行为在零长度(空字符串)匹配后继续匹配的位置上有所不同。
任何一种选择(从相同的字符位置开始而不是从下一个开始)都是合理的 - 请参阅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 '^.*', '"$&"'
, 解决了这个问题。
解决方案
我给出这个答案只是为了演示为什么正则表达式希望允许任何代码出现在$
模式中的最终锚点之后。假设我们需要创建一个正则表达式来匹配具有以下规则的字符串:
- 以三个数字开头
- 后跟一个或多个字母、数字、连字符或下划线
- 仅以字母和数字结尾
我们可以写出以下模式:
^\d{3}[A-Za-z0-9\-_]*[A-Za-z0-9]$
但这有点笨重,因为我们必须使用彼此相邻的两个相似的字符类。相反,我们可以将模式写成:
^\d{3}[A-Za-z0-9\-_]+$(?<!_|-)
或者
^\d{3}[A-Za-z0-9\-_]+(?<!_|-)$
在这里,我们消除了其中一个字符类,而是在锚点后$
使用否定的lookbehind来断言最终字符不是下划线或连字符。
除了向后看之外,对我来说,为什么正则表达式引擎会允许在$
锚点之后出现某些东西是没有意义的。我的观点是,正则表达式引擎可能允许后向出现在 . 之后$
,并且在某些情况下这样做在逻辑上是有意义的。
推荐阅读
- php - 仅从 laravel 验证器获取经过验证的数据
- r - R中的fpgrowth错误
- java - Hibernate:@AuditTable 和 @AuditJoinTable 有什么区别
- c# - 工厂模式,避免不同接口使用相同的开关盒
- sql - MS Query Excel 访问保留字、拼写错误或标点错误
- android - 显示计数中的数字的图像
- android - RxJava2 合并的 Observable 不会停止
- ios - 如何在swift 4中从设备容器中的文档文件夹中打开pdf文件
- r - 翻转后如何调整axis.title.y的垂直位置?
- api - 允许没有 API 的 Instagram 自动化?