Как сделать произвольное регулярное выражение Perl полностью не захватывающим? (Ответ: вы не можете) - PullRequest
8 голосов
/ 24 августа 2010

Как убрать захват из произвольно вложенных подгрупп в строке регулярного выражения Perl? Я хотел бы вложить любое регулярное выражение в обволакивающее выражение, которое охватывает под-регулярное выражение как целую сущность, а также статически известные последующие группы. Нужно ли вручную преобразовывать строку регулярного выражения в использование всех не захватывающих групп (?:) (и надеюсь, что я не ошибаюсь), или существует механизм Perl для регулярных выражений или библиотек, обеспечивающий это?

# How do I 'flatten' $regex to protect $2 and $3?
# Searching 'ABCfooDE' for 'foo' OK, but '((B|(C))fo(o)?(?:D|d)?)', etc., breaks.
# I.E., how would I turn it effectively into '(?:(?:B|(?:C))fo(?:o)?(?:D|d)?)'?
sub check {
  my($line, $regex) = @_;
  if ($line =~ /(^.*)($regex)(.*$)/) {
    print "<", $1, "><", $2, "><", $3, ">\n";
  }
}

Приложение: Я смутно осведомлен о $&, $&#96; и $', и мне посоветовали избегать их, если это возможно, и у меня нет доступа к ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} в моей среде Perl 5.8. Приведенный выше пример можно разбить на 2/3 фрагментов, используя такие методы, и в более сложных реальных случаях это можно было бы повторить вручную, но я думаю, что я хотел бы получить общее решение, если это возможно.

Принятый ответ: То, что я желал, существовало и, на удивление (по крайней мере для меня), не существует, является инкапсулирующей группой, которая делает ее содержимое непрозрачным, так что последующие позиционные обратные ссылки рассматривают содержимое как единый объект ссылки на имена разграничены. gbacon имеет потенциально полезный обходной путь для Perl 5.10+, а FM показывает ручной итерационный механизм для любой версии, которая может выполнить то же самое эффект в определенных случаях, но j_random_hacker называет это тем, что не существует реального языкового механизма для инкапсуляции подвыражений.

Ответы [ 6 ]

8 голосов
/ 24 августа 2010

В общем, вы не можете.

Даже если бы вы могли преобразовать все (...) с в (?:...) с, это не сработало бы в общем случае, потому что шаблон может потребовать обратных ссылок : например, /(.)X\1/, который соответствует любому символу, за которым следует X, за которым следует первоначально сопоставленный символ.

Таким образом, отсутствует механизм Perl для отбрасывания захваченных результатов«по факту», нет способа решить вашу проблему для всех регулярных выражений.Лучшее, что вы можете сделать (или могли бы сделать, если бы у вас был Perl 5.10), это использовать предложение gbacon и надеяться сгенерировать уникальное имя для буфера захвата.

7 голосов
/ 24 августа 2010

Один из способов защитить ваши подшаблоны - использовать именованные буферы захвата :

Кроме того, начиная с Perl 5.10.0 вы можете использовать именованные буферы захвата и именованные обратные ссылки. Обозначение: (?<name>...) для объявления и \k<name> для ссылки. Вы также можете использовать апострофы вместо угловых скобок для определения имени; и вы можете использовать синтаксис обратных ссылок \g{name} в квадратных скобках. Можно также ссылаться на именованный буфер захвата по абсолютному и относительному числу. Вне шаблона именованный буфер захвата доступен через хеш %+. Если разные буферы в одном и том же шаблоне имеют одинаковые имена, $+{name} и \k<name> относятся к крайней левой определенной группе.

В контексте вашего вопроса check становится

sub check {
  use 5.10.0;  
  my($line, $regex) = @_;
  if ($line =~ /(^.*)($regex)(.*$)/) {
    print "<", $+{one}, "><", $+{two}, "><", $+{three}, ">\n";
  }
}

Затем вызывая его с

my $pat = qr/(?<one>(?<two>B|(?<three>C))fo(o)?(?:D|d)?)/;   
check "ABCfooDE", $pat;

выходы

<CfooD><C><C>
5 голосов
/ 24 августа 2010

Это не относится к общему случаю, но ваш конкретный пример может быть обработан с помощью опции /g в скалярном контексте, которая позволит вам разделить задачу на два совпадения, причем второе поднимется там, где остановилось первое:

sub check {
    my($line, $regex) = @_;
    my ($left_side, $regex_match) = ($1, $2) if $line =~ /(^.*)($regex)/g;
    my $right_side = $1 if $line =~ /(.*$)/g;
    print "<$left_side> <$regex_match> <$right_side>\n"; # <AB> <CfooD> <E123>
}

check( 'ABCfooDE123', qr/((B|(C))fo(o)?(?:D|d)?)/ );
2 голосов
/ 24 августа 2010

Если вам нужна только часть строки до и после сопоставления, вы можете использовать массивы @ - и @ + , чтобы получить смещения в сопоставляемой строке:

sub check {
    my ($line, $regex) = @_;
    if ($line =~ /$regex/) {
        my $pre   = substr $line, 0, $-[0];
        my $match = substr $line, $-[0], $+[0] - $-[0];
        my $post  = substr $line, $+[0];
        print "<$pre><$match><$post>\n";
    }
}
1 голос
/ 05 февраля 2016

Perl версии> 5.22, как сообщается, имеет модификатор '/ n', который отключает всю запись.

0 голосов
/ 24 августа 2010

Это не отключает захват, но может выполнить то, что вы хотите:

$ perl -wle 'my $_ = "123abc"; /(\d+)/ && print "num: $1"; { /([a-z]+)/ && print "letter: $1"; } print "num: $1";'
num: 123
letter: abc
num: 123

Вы создаете новую область, и $ 1 за ее пределами не будет затронут.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...