Perl нежадный

У меня проблема с нежадным регулярным выражением (регулярным выражением). Я видел, что есть вопросы относительно нежадных регулярных выражений, но они не отвечают на мою проблему.

Проблема: я пытаюсь сопоставить href привязки lol.

Примечание. Я знаю, что это можно сделать с помощью модулей синтаксического анализа HTML Perl, и мой вопрос не о синтаксическом анализе HTML в Perl. Мой вопрос касается самого регулярного выражения, а HTML - всего лишь пример.

Тестовый пример: у меня есть четыре теста для .*? и [^"]. Два первых дают ожидаемый результат. Однако третий нет, а четвертый просто делает, но я не понимаю, почему.

  1. Почему третий тест не проходит как для .*?, так и для [^"]? Разве не должен работать не жадный оператор?
  2. Почему четвертый тест работает в обоих тестах для .*? и [^"]? Я не понимаю, почему включение .* впереди меняет регулярное выражение (третий и четвертый тесты такие же, за исключением .* впереди).

Я, наверное, не совсем понимаю, как работают эти регулярные выражения. В рецепте Perl Cookbook что-то упоминается, но я не думаю, что это отвечает на мой вопрос.

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 ;

person vkats    schedule 14.05.2011    source источник
comment
Вы формулируете проблему и говорите, что есть решение, которое дает ожидаемый результат. Я не уверен, в чем вопрос.   -  person musiKk    schedule 14.05.2011
comment
Вы правы, я был недостаточно точен. Я отредактировал и более четко сформулировал вопрос.   -  person vkats    schedule 14.05.2011


Ответы (4)


Попробуйте распечатать $& (текст, совпадающий со всем регулярным выражением), а также $1. Это может дать вам лучшее представление о том, что происходит.

Проблема, с которой вы столкнулись, заключается в том, что .*? не означает «Найдите совпадение из всех возможных совпадений, в котором используется наименьшее количество символов». Это просто означает: «Сначала попробуйте сопоставить здесь 0 символов и продолжить сопоставление с остальной частью регулярного выражения. Если это не удается, попробуйте сопоставить 1 символ. Если остальная часть регулярного выражения не соответствует, попробуйте здесь 2 символа и т. Д. "

Perl всегда находит совпадение, которое начинается ближе всего к началу строки. Поскольку большинство ваших шаблонов начинаются с href=, он найдет первый href= в строке и посмотрит, есть ли способ развернуть повторения, чтобы совпадение начиналось там. Если не удается найти совпадение, он пытается начать со следующего href= и так далее.

Когда вы добавляете жадный .* в начало регулярного выражения, сопоставление начинается с того, что .* захватывает как можно больше символов. Затем Perl возвращается в поисках href=. По сути, это заставляет его сначала пробовать последний href= в строке и работать в направлении начала строки.

person cjm    schedule 14.05.2011
comment
Спасибо, кажется, проблема. Это хорошо объясняет первое совпадение и возврат. - person vkats; 14.05.2011
comment
Следует иметь в виду, что жадный / не жадный никогда не меняет, будет ли совпадение успешным или неудачным. Если он преуспеет жадным, он преуспеет и не жадным. Если он потерпит неудачу из-за жадности, он потерпит неудачу из-за жадности. Жадность вступает в игру только тогда, когда существует более одного способа сопоставления в текущей позиции (слева направо). В этом случае жадный поиск соответствует самому длинному из возможных совпадений в этой точке, а нежадный - самому короткому из возможных совпадений в этот момент. - person tadmc; 14.05.2011
comment
@cjm: Спасибо, это первый ответ, который я вижу на эту тему, и это реальный ответ о том, почему это не работает и как заставить его работать. В других вопросах и ответах на ту же проблему люди просто предлагают другое решение, а не настоящий ответ. - person Francisco Zarabozo; 03.04.2013

Работает только четвертый тестовый пример.

Первый: m~href="(.*?)"~s

Это будет соответствовать первому href в вашей строке и захватывать то, что находится между кавычками, так: /hoh/hoh/hoh/hoh/hoh

Второй: m~href="(.*?)".*>lol~s

Это будет соответствовать первому href в вашей строке и захватывать то, что находится между кавычками. Затем он сопоставляет любое количество любых символов, пока не найдет >lol так: /hoh/hoh/hoh/hoh/hoh

Попробуйте захватить .* с помощью m~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"

Взгляните на этот сайт, где объясняется, что делают ваши регулярные выражения.

person Toto    schedule 14.05.2011
comment
Спасибо за ответ. Вы упоминаете что происходит (я уже понимаю это), но не упоминаете почему. Возможно, мой вопрос был написан нечетко, поэтому я его отредактировал. - person vkats; 14.05.2011
comment
@vkats: Я бы сказал, потому что регулярное выражение работает именно так :-). Он пытается сопоставить самое первое вхождение того, что вы ищете. - person Toto; 14.05.2011
comment
Я знаю, что он пытается соответствовать тому, что я ему сказал. Очевидно, я не понимаю, что я сказал, это совпадение, и это то, что я пытаюсь сделать. - person vkats; 14.05.2011

Основная проблема заключается в том, что вы используете нежадные регулярные выражения, хотя этого не следует делать. Вторая проблема заключается в использовании . с *, которые могут случайно совпадать с тем, что вы намеревались. Флаг s, который вы используете, делает . еще более подходящим.

Использовать:

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

для вашего случая. А что касается нежадных регулярных выражений, рассмотрим этот код:

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

Это не соответствует "xaaac", как вы могли ожидать. Он будет начинаться с начала строки и соответствовать «xaaaaab xaaac». Жадный вариант соответствует всей строке.

Дело в том, что хотя нежадные регулярные выражения не пытаются захватить столько, сколько могут, они все же пытаются каким-то образом соответствовать с тем же рвением, что и их жадные братья. И они будут хвататься за любую часть веревки, чтобы сделать это.

Вы также можете рассмотреть притяжательный квантификатор, который отключает отслеживание с возвратом.

Кроме того, полезно начинать с кулинарных книг, но если вы хотите понять, как все работает на самом деле, вам следует прочитать это - perlre.

person Suor    schedule 14.05.2011
comment
Спасибо за ответ (согласен с другим данным несколько секунд назад :)). Я забыл, что матч начинается слева. - person vkats; 14.05.2011

Позвольте мне попытаться проиллюстрировать, что здесь происходит (см. Другие ответы, почему это происходит):

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

person gangabass    schedule 14.05.2011
comment
Действительно, ваше предложение href="[^"]*"[^>]*>lol работает. href="[^"]+"[^>]+>lol+ вместо *) меняет значение? - person vkats; 14.05.2011
comment
@vkats у меня отлично работает. Я использую * вместо + из-за href="">lol - person gangabass; 14.05.2011