4

我遇到了非贪婪正则表达式(正则表达式)的问题。我看到有关于非贪婪正则表达式的问题,但他们没有回答我的问题。

问题:我正在尝试匹配“lol”锚的href。

注意:我知道这可以通过 Perl HTML 解析模块来完成,我的问题不是关于在 Perl 中解析 HTML。我的问题是关于正则表达式本身,而 HTML 只是一个例子。

测试用例:我有四个测试.*?[^"]。两者首先产生了预期的结果。但是第三个没有,第四个只是,但我不明白为什么。

  1. 为什么.*?第三个测试在和的两个测试中都失败了[^"]?非贪婪的操作员不应该工作吗?
  2. 为什么第四个测试在 和 的测试中都.*?有效[^"]?我不明白为什么.*在前面包含 a 会改变正则表达式(第三个和第四个测试是相同的,除了.*前面的)。

我可能不完全理解这些正则表达式是如何工作的。Perl Cookbook recipe提到了一些东西,但我不认为它回答了我的问题。

use strict;

my $content=<<EOF;
<a href="/hoh/hoh/hoh/hoh/hoh" class="hoh">hoh</a>
<a href="/foo/foo/foo/foo/foo" class="foo">foo </a>
<a href="/bar/bar/bar/bar/bar" class="bar">bar</a>
<a href="/lol/lol/lol/lol/lol" class="lol">lol</a>
<a href="/koo/koo/koo/koo/koo" class="koo">koo</a>
EOF

print "| $1 | \n\nThat's ok\n" if $content =~ m~href="(.*?)"~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nThat's ok\n" if $content =~ m~href="(.*?)".*>lol~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nWhy does not the 2nd non-greedy '?' work?\n"
  if $content =~ m~href="(.*?)".*?>lol~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nIt now works if I put the '.*' in the front?\n"
  if $content =~ m~.*href="(.*?)".*?>lol~s ;

print "\n###################################################\n";
print "Let's try now with [^]";
print "\n###################################################\n\n";


print "| $1 | \n\nThat's ok\n" if $content =~ m~href="([^"]+?)"~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nThat's ok.\n" if $content =~ m~href="([^"]+?)".*>lol~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nThe 2nd greedy still doesn't work?\n"
  if $content =~ m~href="([^"]+?)".*?>lol~s ;

print "\n---------------------------------------------------\n";

print "| $1 | \n\nNow with the '.*' in front it does.\n"
  if $content =~ m~.*href="([^"]+?)".*?>lol~s ;
4

4 回答 4

6

尝试打印出$&(与整个正则表达式匹配的文本)以及$1. 这可能会让您更好地了解正在发生的事情。

您似乎遇到的问题.*?并不意味着“在此处使用最少字符的所有可能匹配项中查找匹配项”。它只是意味着“首先,在这里尝试匹配 0 个字符,然后继续匹配正则表达式的其余部分。如果失败,请尝试匹配 1 个字符。如果正则表达式的其余部分不匹配,请在此处尝试 2 个字符。等等。 "

Perl总是会找到最接近字符串开头的匹配。由于您的大多数模式都以 开头href=,因此它将找到href=字符串中的第一个,并查看是否有任何方法可以扩展重复以从那里开始匹配。如果无法匹配,它将尝试从下一个开始href=,依此类推。

当您.*在正则表达式的开头添加一个贪心字符时,匹配从.*尽可能多地抓取字符开始。Perl 然后回溯到找到一个href=. 本质上,这会导致它首先尝试字符串中的最后 href=一个,然后朝着字符串的开头工作。

于 2011-05-14T10:29:48.727 回答
0

只有第四个测试用例有效。

首先:m~href="(.*?)"~s

这将匹配字符串中的第一个 href 并捕获引号之间的内容,因此:/hoh/hoh/hoh/hoh/hoh

第二:m~href="(.*?)".*>lol~s

这将匹配字符串中的第一个 href 并捕获引号之间的内容。然后它匹配任意数量的任意字符,直到找到>lol/hoh/hoh/hoh/hoh/hoh

尝试捕获.*withm~href="(.*?)"(.*)>lol~s

$1 contains:
/hoh/hoh/hoh/hoh/hoh
$2 contains: 
class="hoh">hoh</a>
<a href="/foo/foo/foo/foo/foo" class="foo">foo </a>
<a href="/bar/bar/bar/bar/bar" class="bar">bar</a>
<a href="/lol/lol/lol/lol/lol" class="lol" 

第三:m~href="(.*?)".*?>lol~s

与上一个测试用例的结果相同。

第四个:m~.*href="(.*?)".*?>lol~s

这将匹配任意数量的任意字符,然后href=",然后捕获任意数量的非贪婪字符,直到引用,然后匹配任意数量的任意字符,直到找到>lol/lol/lol/lol/lol/lol

.*尝试捕获所有m~(.*)href="(.*?)"(.*?)>lol~s

$1 contains:
<a href="/hoh/hoh/hoh/hoh/hoh" class="hoh">hoh</a>
<a href="/foo/foo/foo/foo/foo" class="foo">foo </a>
<a href="/bar/bar/bar/bar/bar" class="bar">bar</a>
<a
$2 contains: 
/lol/lol/lol/lol/lol
$3 contains:
class="lol"

看看这个网站,它解释了你的正则表达式在做什么。

于 2011-05-14T10:03:32.857 回答
0

主要问题是您不应该使用非贪婪的正则表达式。第二个问题是使用.with *which 可能会意外匹配更多您想要的内容。s您使用的标志.更加匹配。

采用:

m~href="([^"]+)"[^>]*>lol~

对于你的情况。关于非贪婪的正则表达式,请考虑以下代码:

$_ = "xaaaaab xaaac xbbc";
m~^x.+?c~;

如您所料,它与“xaaac”不匹配。它将从字符串的开头开始并匹配“xaaaaab xaaac”。贪婪的变体将匹配整个字符串。

关键是,尽管非贪婪的正则表达式不会尽可能多地抢夺,但它们仍然试图以某种方式与贪婪的兄弟一样渴望匹配。他们会抓住绳子的任何部分来做这件事。

您还可以考虑关闭回溯的“占有”量词。

此外,烹饪书是很好的开始,但如果你想了解事情的真正运作方式,你应该阅读这个 - perlre

于 2011-05-14T10:36:32.290 回答
0

让我尝试说明这里发生了什么(请参阅其他答案为什么会发生):

href="(.*?)"

比赛:href="/hoh/hoh/hoh/hoh/hoh"
组:/hoh/hoh/hoh/hoh/hoh

href="(.*?)".*>lol

匹配:href="/hoh/hoh/hoh/hoh/hoh" class="hoh">hoh</a> <a href="/foo/foo/foo/foo/foo" class="foo">foo </a> <a href="/bar/bar/bar/bar/bar" class="bar">bar</a> <a href="/lol/lol/lol/lol/lol" class="lol">lol

团体:/hoh/hoh/hoh/hoh/hoh

href="([^"]+?)".*?>lol

匹配:href="/hoh/hoh/hoh/hoh/hoh" class="hoh">hoh</a> <a href="/foo/foo/foo/foo/foo" class="foo">foo </a> <a href="/bar/bar/bar/bar/bar" class="bar">bar</a> <a href="/lol/lol/lol/lol/lol" class="lol">lol

团体:/hoh/hoh/hoh/hoh/hoh

.*href="(.*?)".*?>lol

匹配:<a href="/hoh/hoh/hoh/hoh/hoh" class="hoh">hoh</a> <a href="/foo/foo/foo/foo/foo" class="foo">foo </a> <a href="/bar/bar/bar/bar/bar" class="bar">bar</a> <a href="/lol/lol/lol/lol/lol" class="lol">lol

团体:/lol/lol/lol/lol/lol

编写您想要的正则表达式的一种方法是使用:href="[^"]*"[^>]*>lol

于 2011-05-14T11:06:48.540 回答