【问题标题】:Perl non-greedyPerl 非贪婪
【发布时间】:2011-08-25 10:36:30
【问题描述】:

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

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

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

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

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

我可能不完全理解这些正则表达式是如何工作的。 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 ;

【问题讨论】:

  • 你陈述了一个问题,并说有一个产生预期结果的解决方案。我不确定问题是什么。
  • 你说得对,我不够精确。我更清楚地编辑并陈述了这个问题。

标签: regex perl non-greedy regex-greedy


【解决方案1】:

让我试着说明这里发生了什么(查看其他答案为什么会发生):

href="(.*?)"

匹配:href="/hoh/hoh/hoh/hoh/hoh"
群:/hoh/hoh/hoh/hoh/hoh

href="(.*?)".*&gt;lol

匹配:href="/hoh/hoh/hoh/hoh/hoh" class="hoh"&gt;hoh&lt;/a&gt; &lt;a href="/foo/foo/foo/foo/foo" class="foo"&gt;foo &lt;/a&gt; &lt;a href="/bar/bar/bar/bar/bar" class="bar"&gt;bar&lt;/a&gt; &lt;a href="/lol/lol/lol/lol/lol" class="lol"&gt;lol

群组:/hoh/hoh/hoh/hoh/hoh

href="([^"]+?)".*?&gt;lol

匹配:href="/hoh/hoh/hoh/hoh/hoh" class="hoh"&gt;hoh&lt;/a&gt; &lt;a href="/foo/foo/foo/foo/foo" class="foo"&gt;foo &lt;/a&gt; &lt;a href="/bar/bar/bar/bar/bar" class="bar"&gt;bar&lt;/a&gt; &lt;a href="/lol/lol/lol/lol/lol" class="lol"&gt;lol

群组:/hoh/hoh/hoh/hoh/hoh

.*href="(.*?)".*?&gt;lol

匹配:&lt;a href="/hoh/hoh/hoh/hoh/hoh" class="hoh"&gt;hoh&lt;/a&gt; &lt;a href="/foo/foo/foo/foo/foo" class="foo"&gt;foo &lt;/a&gt; &lt;a href="/bar/bar/bar/bar/bar" class="bar"&gt;bar&lt;/a&gt; &lt;a href="/lol/lol/lol/lol/lol" class="lol"&gt;lol

群组:/lol/lol/lol/lol/lol

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

【讨论】:

  • 您的提议href="[^"]*"[^&gt;]*&gt;lol 确实有效。 href="[^"]+"[^&gt;]+&gt;lol(用+而不是*)会改变含义吗?
  • @vkats 它对我来说很好用。我使用* 而不是+ 因为href=""&gt;lol
【解决方案2】:

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

用途:

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

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

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

它不会像您预期的那样匹配“xaaac”。它将从字符串的开头开始并匹配“xaaaaab xaaac”。贪婪的变体会匹配整个字符串。

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

您也可以考虑“占有”量词,它会关闭回溯。

此外,烹饪书是很好的起点,但如果您想了解事情的真正运作方式,您应该阅读这篇文章 - perlre

【讨论】:

  • 感谢您的回答(它在几秒钟前与另一个给定的 :) 一致)。我忘了比赛是从左边开始的。
【解决方案3】:

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

第一个:m~href="(.*?)"~s

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

第二个:m~href="(.*?)".*&gt;lol~s

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

尝试使用m~href="(.*?)"(.*)&gt;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="(.*?)".*?&gt;lol~s

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

第四位:m~.*href="(.*?)".*?&gt;lol~s

这将匹配任意数量的任意字符,然后是href=",然后非贪婪地捕获任意数量的任意字符,直到引用,然后匹配任意数量的任意字符,直到找到&gt;lol,所以:@987654334 @

尝试用m~(.*)href="(.*?)"(.*?)&gt;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"

看看this site,它解释了你的正则表达式在做什么。

【讨论】:

  • 感谢您的回答。你提到了发生了什么(我已经明白了),但没有提到为什么会发生。可能我的问题没有写清楚,所以我编辑了它。
  • @vkats:我会说因为正则表达式是这样工作的:-)。它试图匹配您要搜索的内容的第一次出现。
  • 我知道它会尝试匹配我告诉它匹配的内容。显然,我不明白我告诉它匹配的内容,这就是我尝试做的。
【解决方案4】:

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

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

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

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

【讨论】:

  • 谢谢,这似乎是问题所在。它很好地解释了第一次匹配和回溯。
  • 要记住的一件好事是贪婪/非贪婪永远不会改变匹配是否成功。如果它贪婪成功,它将成功非贪婪。如果它贪婪失败,它将失败非贪婪。只有在当前位置有多种匹配方式(从左到右)时,贪婪才会发挥作用。在这种情况下,贪婪匹配该点可能匹配中最长的匹配项,而非贪婪匹配该点可能匹配项中最短的匹配项。
  • @cjm:谢谢,这是我在该主题上看到的第一个答案,它是关于为什么它不起作用以及如何使它起作用的实际答案。在其他相同问题的问答中,人们只是提供不同的解决方案,而不是真正的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多