为什么 Perl 正则表达式中的 `*?` 是贪婪的?

8

我运行一个简单的程序:

my $_ =  '/login/.htaccess/.htdf';
s!(/\.ht.*?)$!/!;
print "$_ $1";

输出
/login/ /.htaccess/.htdf

我希望这个正则表达式只匹配 /.htdf

例子2:

my $_ =  'abcbc';
m/(b.*?)$/;
print "$_ $1\n";

输出
abcbc bcbc

我期望的是 bc

为什么 *? 仍然贪婪匹配?(我想要最小匹配。)


2
正则表达式存在从左到右的偏向性。 "Minimal"意味着找到第一个匹配项。所以它找到第一个“b”,然后因为.*?$匹配余下的行。 - user557597
2
顺便提一下,你现在使用的方式,“.*?$”与“.*”完全等价。也就是说,它们都是贪婪匹配。 - user557597
你实际上在指定想要捕获行末之前的全部内容。因此,它要么匹配所有内容,要么失败,并且正则表达式会尽可能地匹配。 - Axeman
1
\Ks{\K/\.ht[^/]*$}{}中是无用的。如果您不想要尾随的 /,请使用s{/\.ht[^/]*$}{} - ikegami
在perlre中是否有从左到右到从右到左的开关? - Eugen Konkov
4个回答

8

原子按顺序匹配,每个原子在前一个原子匹配结束的位置后必须匹配。 (第一个原子隐式地由\A(?s:.)*?前置。)这意味着.*/.*?不能决定其开始匹配的位置;它只能决定停止匹配的位置。

示例1

它不是贪婪的。\.ht将匹配带到位置10,并且在位置10,最小的.*?可以匹配并仍然具有其余模式的匹配是access/.htdf。 实际上,在位置10,只有.*?可以匹配,仍然具有其余模式的匹配。

如果路径的最后一部分以.ht开头,则应删除该部分,保留前面的/。 为此,您可以使用以下任一方法:

s{/\.ht[^/]*$}{/}

或者
s{/\K\.ht[^/]*$}{}

例子 2

这并不是贪心。 b 将匹配带到位置 2,而在 位置 2,最小的 .*? 可以匹配并仍然具有其余模式匹配的内容是 cbc。事实上,它是唯一可以在 位置 2 匹配并仍然具有其余模式匹配的东西。

你可能正在寻找

/b[^b]*$/

或者

/b(?:(?!b).)*$/    # You'd use this if "b" was really more than one char.

1
您可以使用负向先行断言来实现此功能:

~/(\.ht(?!.*\.ht).*)$~

正则表达式演示

(?!.*\.ht)是一个负向前瞻,确保在.ht之后没有.ht出现,从而确保只匹配最后一个.ht

.*?如果右侧有某种模式,则会变成非贪婪模式。

代码:

$str = '/login/.htaccess/.htdf';

$str =~ s~/(\.ht(?!.*\.ht).*)$~/~m;

print "$str\n";

1
(?!.*\.ht) 非常容易出错。(例如,如果您将 /s 添加到该模式中,它将会出错。)(?:(?!\.ht).) 更加健壮。 - ikegami
1
是的,同意,但这只有在输入为多行且还使用s的情况下才是正确的,否则(?:(?! \ .ht)\))(?! \。* \ .ht)慢得多。 - anubhava
它会在很多其他情况下出问题。我想学习那些情况。 - anubhava
请提供一些示例输入。 - anubhava
你可以保留你的观点,但我没有看到任何明显的情况,这个正则表达式无法匹配单行文本中最后一个出现的文本。 - anubhava
显示剩余7条评论

1
为什么不行呢?贪婪模式是向前匹配,而非向后。在非贪婪模式下,状态机开始匹配并在每一步进行检查,而不是全部吞噬然后回溯,但这并不能保证你得到“最小匹配”。
也许您想避免匹配“/”?就像s{/\.ht[^/]*$}{/}中的一样。

0

正则表达式的工作方式与您所做的相同。
但是,如果要使用点元字符,则必须贪婪匹配。

这应该可以工作:s!.*/\K\.ht.*$!!它基本上截掉了末尾的.ht...

如果您想要更具体,您需要s!/\K\.htdf$!!


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