".*?" 正则表达式实际上是什么意思?

6

我已经使用Perl十年了。但是最近我对使用.*?正则表达式感到困惑。

它似乎不能匹配最少数量的字符。有时它会给出不同的结果。

例如,对于这个字符串:aaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmbaaaaaaaaaaaaaaaaaaaaaab 和模式:a.*?b,它在两个组中完全匹配输入字符串。根据定义,它应该匹配最后的 "ab"。


@Kobi - 我认为这不是同一个问题。OP问的是为什么.*?不总是匹配最少数量的字符,而不是?的目的是什么。 - Ted Hopp
@Ted - 说得对,但我认为如果你理解.*?的工作原理,你就可以轻松回答那个问题。在这种情况下,可能是同一用户提出的这个问题的概括:https://dev59.com/glXTa4cB1Zd3GeqP2H2I - Kobi
这并不会引出我想要的答案。这就是为什么我开始了一个新的但相关的线程。 - AgA
6个回答

12
它不会导致a.*?b匹配尽可能少的字符;它会导致.*匹配尽可能少的字符。由于它只影响.*,它对已经匹配的内容(即由a匹配的内容)没有影响。
示例简化为:
#01234
'aaab' =~ /a.*?b/

发生了什么:
1. 在位置0,a匹配了1个字符(a)。 2. 在位置1,.*?匹配了0个字符(空字符串)。 3. 在位置1,b无法匹配。⇒ 回溯 4. 在位置1,.*?匹配了1个字符(a)。 5. 在位置2,b无法匹配。⇒ 回溯 6. 在位置1,.*?匹配了2个字符(aa)。 7. 在位置3,b匹配了1个字符(b)。 8. 模式匹配成功。
正如您所看到的,它尝试匹配了零个字符,这显然是最小可能的匹配。但是当这样做时,整体模式未能匹配,因此尝试了越来越大的匹配,直到整体模式匹配成功。
我尽量避免使用非贪婪修饰符。
'aaab' =~ /a[^ab]*b/

如果a确实是更复杂的东西,那么可以使用负向前瞻。
'aaab' =~ /a(?:(?!a|b).)*b/

2
同意避免使用非贪婪修饰符。不仅在效率上通常更好,使用否定字符类或负向前瞻(它避免了回溯)还能更清晰地记录你对未来维护程序员的意图。 - Dave Sherohman
回溯是什么意思? - Golden Lion
@Golden Lion,这在文档中已经很好地解释了。基本上,它是指撤销尝试过的事情,以尝试不同的事情。 - ikegami
我正在尝试在字符串中查找列表和列表中的列表。如何使用回溯实现呢?[[1.5,1.5],[2.1,2.1]],[1, 2] - Golden Lion

9
它的意思是:
.   # match any character except newlines
*   # zero or more times
?   # matching as few characters as possible

So in

<tag> text </tag> more text <tag> even more text </tag>

正则表达式 <tag>(.*)</tag> 会一次性匹配整个字符串,并捕获其中的内容。
 text </tag> more text <tag> even more text 

在反向引用编号1中。

如果您使用<tag>(.*?)</tag>进行匹配,您将获得两个匹配项:

  1. <tag> text </tag>
  2. <tag> even more text </tag>

分别仅捕获texteven more text在反向引用编号1中。

如果(感谢Kobi!)您的源文本是

<tag> text <tag> nested text </tag> back to first level </tag>

如果你使用<tag>(.*)</tag>进行匹配,你会发现它会再次匹配整个字符串,但是使用<tag>(.*?)</tag>则只会匹配

<tag> text <tag> nested text </tag>

由于正则表达式引擎从左到右工作,这就是为什么正则表达式不是匹配上下文无关文法的最佳工具之一。


如果我理解正确,OP更感兴趣的是<tag><tag>text</tag> - 在“懒惰”和“最优化最小化”之间存在混淆。 - Kobi
蒂姆,谢谢你。我也应该接受你的答案,因为它非常清晰易懂,但是......在这里我们只能接受一个答案。 - AgA

4

它匹配最少数量的字符,从第一个能匹配的位置开始,这使得正则表达式的其余部分可以匹配。那个中间部分(从...开始)是正则表达式状态机运作方式的本质。(为了进一步澄清而编辑)


1
这是正确的“理论”定义。但实际情况并非完全如此。例如对于此字符串:aaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmbaaaaaaaaaaaaaaaaaaaaaab和模式:a.*?b,它将匹配两个组中的完整输入字符串。根据定义,它应该匹配最后的“ab”。 - AgA
1
好的,你还是有一部分没理解:如果没有其他内容存在,它将从可能的最早位置开始缩短。(换句话说,它是从结尾开始缩短,而不是从开头开始。) - geekosaur
1
用户656848,请注意正则表达式引擎如果可以的话从第一个字符开始匹配并向前移动。 .*? 并不能保证最短的匹配,而是在该位置上消耗尽可能少的元素,同时仍然允许其余的正则表达式匹配。 - sarnold
2
不,正则表达式是从左到右应用的。使其变为懒惰匹配并不意味着正则表达式将匹配最短的字符串,而是“从字符串中当前位置开始的最短可能匹配”。因此,在您的示例中,您将获得aaaaaaaaaaaaaaaaaaaaaaaaaab作为第二个匹配项。 - Tim Pietzcker
@user656848:请将这个示例放入你的问题中。这是相关的信息。仅仅说“它不起作用”会给新手留下疑虑,而这是一个非常容易被误解或滥用的好工具。 - Merlyn Morgan-Graham

1

我认为在你的情况下不能直接匹配ab。通常当.*?无法工作时,需要使用[^c]*模式,其中c是一个字符或字符类。这可以防止误匹配。

但在这种情况下,它不起作用:a[^a]*b首先匹配ammmmmmmmmmmb。因此,找到最短匹配的唯一方法是找到所有匹配,然后选择最短的。

以下是一种详细的(你说你有一段时间没有使用Perl了;--)获取所需结果的方法:

#!/usr/bin/perl 

use strict;
use warnings;

use List::Util qw(reduce); # see List::Util docs for what reduce does

my $s= "aaaaaaaaaaaaaaaaaaaaaaammmmmmmmmmmbaaaaaaaaaaaaaaaaaaaaaab";

my $RE= qr/a[^a]*b/;

print "regexp: $RE\n";                 # ammmmmmmmmmmb
print "single match:\n";
if( $s=~ m{($RE)}) { print "  $1\n"; } 

print "all matches (loop):\n";         # ammmmmmmmmmmb \n ab
while( $s=~ m{($RE)}g)
  { print "  - $1\n"; }

print "all matches (in an array):\n";  # ammmmmmmmmmmb - ab
my @matches= $s=~ m{(a[^a]*b)}g;
if( @matches) { print "  ", join( " - ", @matches), "\n"; }

print "\nshortest match: ";            # ab
print reduce { length $a < length $b ? $a : $b } @matches;
print "\n";

简而言之,懒惰匹配并不等同于在字符串中获取最短的匹配。而且,使用像 Perl(我相信大多数其他语言也是如此)这样的正则表达式引擎来解决这个简单的问题并不容易。

1

它应该匹配最少数量的字符,以便整个模式能够成功匹配(如果有匹配的话)。你能提供一个具体的例子,证明它没有做到这一点吗?


@user656848 - 我希望@geekosaur、@sarnold和@Tim Pietzcker对geekosaur的答案的评论能够为您解释清楚正在发生的事情。我同意@Merlyn的观点,您应该编辑原始帖子并添加示例。这可以澄清您的问题。 - Ted Hopp

0
请提供一个具体的例子,以便我们可以重现您正在经历的问题行为。
您正在使用正确的结构,所以也许您查询的其余部分存在问题。我也遇到过类似的问题,但我总是发现这是由于我的愿望解析引起的 - 即我希望正则表达式按照我想要的方式解析,而不是我键入的方式 :)

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