正则表达式:/.+?/ 是如何工作的?

3
如何理解正则表达式'.+?'?其中的.+表示匹配任何字符,而问号?表示非贪婪模式,即只要有一个匹配就停止。因此,这个正则表达式能匹配以下内容:
'cat'
''(即空字符串)

我建议你查看http://www.regular-expressions.info/。 - seize
https://dev59.com/bHM_5IYBdhLWcg3w-4nw - Brad Gilbert
https://dev59.com/KUXRa4cB1Zd3GeqPvNkD#314973 - Brad Gilbert
5个回答

10

"+?" 不是简单的 "+" 量词后面跟着 "?" 量词。而是 "?" 修改 "+" 为执行 "惰性" 或 "非贪婪" 匹配,这意味着匹配最少数量的字符已经足够。

因此,"a+?" 正则表达式只会在 "caaat" 中匹配一个 "a"。


1
汉斯:正确的术语是“量词”,而不是“乘数”。希望有所帮助。 - Stephen C

8
除了Hans Kesting已经说过的,懒惰乘法器将完全相反于正常贪婪乘法器:可能的匹配尽可能小,并测试其余的正则表达式。
因此,如果您有字符串aaba并在其上测试正则表达式a.*b,那么内部处理步骤如下:
1. a在a.*b中匹配aaba。 2. .*在a.*b中匹配aab。 3. .*然后在aab中匹配ab。 4. .*然后在aaba中匹配a。 5. b在a.*b中失败,因为没有字母剩下。
回溯退回一步,.*现在只匹配aaba中的bb。
b在a.*b中仍然无法匹配aab。
回溯退回一步,.*现在只匹配aaba中的b。
b在a.*b中现在匹配aa b a,我们完成了。
因此,完整的匹配是aab a。
如果我们使用懒惰乘数(a.*?b)做同样的事情,处理将相反,尝试尽可能少地匹配字符。
  1. aa.*?b 中匹配到 aaba
  2. .*a.*?b 中匹配不到任何内容(* 表示零个或多个重复项),由于 .* 被声明为懒惰模式(.*?),因此会尝试测试正则表达式的其余部分
  3. ba.*?b 中对 aaba 不起作用
    • 回溯将尝试增加 .* 的匹配
  4. .* 现在匹配到了 aaba
  5. ba.*?b 中匹配到了 aaba,我们完成了。

因此完整匹配是 aaba


那么a.b会匹配aab和ab,对吗?但是a.?b只会匹配aab?(使用您的aaba示例来说明这两种情况)。所以.?并不一定是尝试找到最少可能的字符,而是第一个'.'实例为1个字母,如果匹配成功,则完成。因此,一旦a.?b匹配到aab,它就不会继续尝试仅匹配ab。 - Tony Stark
@hatorade:不好意思,两个表达式只匹配 aab a - Gumbo
@Gumbo:我不明白为什么 .? 不能匹配 ab。a.?b 明显可以匹配 ab。.* 匹配 aab 是有道理的,因为它是贪婪的并试图获得最大的匹配。但这不是懒惰加号的意义吗? - Tony Stark
@hatorade:匹配是在整个字符串上执行的,而不仅仅是子字符串。因此,**aab**a是两个正则表达式的唯一匹配项。 - Gumbo
@Gumbo:嗯,显然根据Python的说法它确实像你所说的那样工作。我想我只需要理解一下它。 - Tony Stark

4

+?(惰性加号)

重复前一个元素一次或多次。这是惰性的,因此引擎首先只匹配前一个元素一次,然后再尝试具有逐渐增加的前一个元素匹配的排列。

/".+?"/ 匹配 abc "def" "ghi" jkl 中的 "def"(和 "ghi"),而 /".+"/ 则匹配 "def" "ghi"

您可以在此处找到更多信息。


2
原始示例中的引号一开始让我感到困惑 - 我以为它们只是将模式与文本分开,而不是模式的一部分。我认为添加斜杠可以澄清意图。 - tvanfosson
似乎Svish编辑过,/".+?"/会匹配"def"和"ghi",对吗?分别匹配。因此它将返回两个表达式。而/".+"/捕获整个短语"def" "ghi",将"def" "ghi"视为.+部分? - Tony Stark
@hatorade,就我在RegexBuddy中测试的情况来看,是的。 - Svish
@Svish 我在 Python 中进行了测试,发现 .+? 似乎只返回 "def",并没有提到捕获 "ghi",但这可能是因为我需要在正则表达式中添加其他内容来指示我想要所有的匹配项。 - Tony Stark

1

关于 Perl 如何处理这些量词,有文档可供参考perldoc perlre

默认情况下,量化子模式是“贪婪”的,也就是说,在允许整个模式匹配的情况下,它将尽可能多地匹配(给定特定的起始位置)。如果您希望它尽可能少地匹配,请在量词后面加上“?”。请注意,意义不会改变,只是“贪婪性”发生了变化:
    *?     匹配 0 次或多次,非贪婪地
    +?     匹配 1 次或多次,非贪婪地
    ??     匹配 0 次或 1 次,非贪婪地
    {n}?   精确匹配 n 次,非贪婪地
    {n,}?  至少匹配 n 次,非贪婪地
    {n,m}? 至少匹配 n 次但不超过 m 次,非贪婪地
默认情况下,当量化子模式不允许整个模式匹配时,Perl 将回溯。然而,这种行为有时是不可取的。因此,Perl 还提供了“占有”量化器形式。
    *+     匹配 0 次或多次,并且不返回任何内容
    ++     匹配 1 次或多次,并且不返回任何内容
    ?+     匹配 0 次或 1 次,并且不返回任何内容
    {n}+   精确匹配 n 次,并且不返回任何内容(冗余)
    {n,}+  至少匹配 n 次,并且不返回任何内容
    {n,m}+ 至少匹配 n 次但不超过 m 次,并且不返回任何内容
例如,
   'aaaa' =~ /a++a/
永远不会匹配,因为 a++ 将吞掉字符串中的所有 a,并且不会留下任何内容供模式的其余部分使用。这个特性非常有用,可以给 Perl 提供关于哪些地方不应该回溯的提示。例如,典型的“匹配双引号字符串”问题可以通过以下方式最有效地执行:
   /"(?:[^"\\]++|\\.)*+"/
因为我们知道如果最后一个引号没有匹配成功,回溯是无济于事的。有关更多详细信息,请参见独立子表达式 (?>...);占有量化器只是该结构的语法糖。例如,上面的示例也可以写成以下形式:
   /"(?>(?:(?>[^"\\]+)|\\.)*)"/

链接


0

不可避免地,正则表达式将寻找至少一个字符。我已经遇到过空字符串无法通过测试的情况,最好使用.*?(.*)?。有时候你必须在问号前的括号中指定可能为空的字符串部分,这会有所帮助。例如,\d{6}?会产生错误的结果,而如果我在字符串中说:(\d{6})?,则会得到正确的结果。

preg_match("/shu\.(\d{6})?/", "shu.321456")

这将产生true,字符串"shu."在句点后没有任何int也是如此。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接