Как я могу несколько раз сопоставить конец строки в регулярном выражении без интерполяции?

если у меня есть ввод с новыми строками, например:

[INFO]
xyz
[INFO]

Как я могу вытащить часть xyz, используя привязки $? Я попробовал такой шаблон, как /^\[INFO\]$(.*?)$\[INFO\]/ms, но perl дает мне:

Use of uninitialized value $\ in regexp compilation at scripts\t.pl line 6.

Есть ли способ отключить интерполяцию, чтобы якоря работали должным образом?

РЕДАКТИРОВАТЬ: Ключевым моментом является то, что якорь конца строки представляет собой знак доллара, но иногда может быть необходимо вставлять якорь конца строки в шаблон. Если шаблон интерполируется, у вас могут возникнуть проблемы, такие как неинициализированный $\. Например, приемлемым решением здесь является /^\[INFO\]\s*^(.*?)\s*^\[INFO\]/ms, но оно не решает сути первой проблемы. Я изменил якоря на ^, поэтому интерполяции не происходит, и с этим вводом я могу это сделать. Но что делать, если я действительно хочу сослаться на EOL с помощью $ в своем шаблоне? Как заставить регулярное выражение компилироваться?


person harschware    schedule 20.05.2010    source источник
comment
Что с минусом? Я думаю, это хороший вопрос.   -  person Alan Moore    schedule 21.05.2010


Ответы (5)


Вопрос академический - в любом случае нет необходимости в якорях $ в вашем регулярном выражении. Вы должны использовать \n для соответствия новым строкам, потому что $ соответствует только промежутку между переводом строки и символом перед ним.

РЕДАКТИРОВАТЬ: я пытаюсь сказать, что вам никогда не понадобится использовать $ таким образом. Любое совпадение, которое простирается от одной строки к другой, должно каким-то образом использовать разделитель строк. Рассмотрим ваш пример:

/^\[INFO\]$(.*?)$\[INFO\]/ms

Если бы это скомпилировалось, (.*?) начал бы с использования первого перевода строки и продолжал бы работать, пока не совпадет с \nxyz, где второй $ завершится успешно. Но следующий символ — это перевод строки, и регулярное выражение ищет [, так что это не работает. После возврата (.*?) неохотно потреблял бы еще один символ — второй перевод строки — но тогда $ терпел неудачу.

Каждый раз, когда вы пытаетесь сопоставить EOL с $, а затем еще с чем-то, первым «материалом», который вам нужно будет сопоставить, будет перевод строки, так почему бы вместо этого не сопоставить его? Вот почему компилятор регулярных выражений Perl пытается интерпретировать $\ как имя переменной в вашем регулярном выражении: нет смысла иметь якорь в конце строки, за которым следует символ, который не является разделителем строк.

person Alan Moore    schedule 20.05.2010
comment
Да, вопрос академический. Я отредактировал сообщение, чтобы показать, что меня интересует только выяснение того, как заставить $ функционировать как конец строки в нескольких местах в регулярном выражении. - person harschware; 21.05.2010
comment
@harschware: см. мой расширенный ответ. - person Alan Moore; 21.05.2010
comment
Я понимаю, что вы имеете в виду, но опять же: как насчет сути проблемы? Как насчет того, если бы регулярное выражение было /^\[INFO\]$\nxyz/ms, тогда $\ интерполируется до undef, и регулярное выражение не соответствует. Проблема не в том, как мне заставить мой шаблон совпадать... а в том, как вы используете $ как EOL в случаях, когда он интерполируется? - person harschware; 21.05.2010
comment
Вы можете отключить интерполяцию, используя одинарные кавычки в качестве разделителей регулярных выражений, т. е. m'^\[INFO\]$\nxyz'm. Но я хочу сказать, что вам не нужно. Perl очень умен в определении того, следует ли интерполировать последовательность или нет. Обратите внимание, что он не пытается интерполировать $(, который также является встроенной переменной. - person Alan Moore; 22.05.2010
comment
На самом деле, ваш комментарий об использовании m'' для указания регулярного выражения - это ответ, который я искал. Я не видел этого комментария перед запуском вознаграждения — я приму этот ответ, когда период ожидания истечет. - person harschware; 01.06.2010

На основе ответа в perlfaq6 — Как я могу вытащить линии между двумя шаблонами, которые сами находятся на разных строках? , вот как будет выглядеть однострочник:

perl -0777 -ne 'print $1,"\n" while /\[INFO\]\s*(.*?)\s*\[INFO\]/sg' file.txt

Переключатель -0777 глотает весь файл сразу.

Однако, если вам нужна подпрограмма, которая дает вам возможность выбирать, какой тег вы хотите извлечь, File::Slurp упрощает задачу:

use strict;
use warnings;
use File::Slurp qw/slurp/;

sub extract {

    my ( $tag, $fileName ) = @_;
    my $text = slurp $fileName;

    my ($info) = $text =~ /$tag\s*(.*?)\s*$tag/sg;
    return $info;
}

# Usage:
extract ( qr/\[INFO\]/, 'file.txt' );
person Zaid    schedule 20.05.2010

Когда регулярные выражения становятся слишком сложными, они, вероятно, являются неправильным инструментом. Я мог бы рассмотреть возможность использования здесь оператора флип-флоп. Оно ложно до тех пор, пока его левая часть не станет истинной, а затем остается истинным, пока его правая часть не станет истинной. Таким образом, вы можете выбрать, где начать и закончить извлечение, просто просматривая отдельные строки:

my $string = <<'HERE';
[INFO]
xyz
[INFO]
HERE

open my $string_fh, '<', \$string;

while( <$string_fh> )
    {
    next if /\[INFO]/ .. /\[INFO]/;
    chomp;

    print "Extracted <$_>\n";
    }

Если вы используете Perl 5.10, вы можете использовать обобщенную строку, оканчивающуюся на \R в регулярном выражении:

use 5.010;

my $string = <<'HERE';
[INFO]
xyz
[INFO]
HERE

my( $extracted ) = $string =~ /(?:\A|\R)\[INFO]\R(.*?)\R\[INFO]\R/;

print "Extracted <$extracted>\n";

Не зацикливайтесь на якоре в конце строки.

person brian d foy    schedule 21.05.2010
comment
Отличное объяснение, не мог понять поведение триггера с регулярными выражениями из perlfaq6 или perlop - person Zaid; 21.05.2010
comment
(+1) Очень круто! Но есть две проблемы: это специфично для Perl6. :-( И это еще один обходной путь (я уже разработал свой собственный обходной путь в посте). На данный момент я действительно просто спрашиваю, как заставить якоря конца строки работать в середине регулярного выражения , Спасибо за образование, хотя. - person harschware; 21.05.2010
comment
Это не только Perl 6, это есть в FAQ по Perl 5. Это тоже не обходной путь. Это простой способ извлечь текст между двумя строками. - person brian d foy; 21.05.2010
comment
Закрывающие квадратные скобки в регулярном выражении должны иметь обратную косую черту - person Zaid; 21.05.2010
comment
Вам не нужно экранировать закрывающую квадратную скобку. Нет ничего особенного в том, что нет специальной открывающей квадратной скобки для начала класса символов. - person brian d foy; 21.05.2010
comment
Думаю, я ошибся насчет того, что это только perl6, извините. Когда я сказал обходной путь, я имел в виду, что он не затрагивает двойную роль $ как EOL и не вызывает интерполяцию в шаблоне, о чем действительно идет речь. Но это довольно умный кусок кода. - person harschware; 21.05.2010
comment
Что ж, не заставляйте вещи делать то, что им трудно делать. Используйте инструменты, которые делают работу естественно. Если регулярные выражения причиняют вам боль, это признак того, что они могут быть неправильным инструментом. - person brian d foy; 22.05.2010
comment
Еще раз, вы можете видеть, что у меня уже есть альтернативное решение, и вы предоставили два других. Этот бит кода приветствуется для написания любым количеством способов, это не главное. Суть заключается в том, чтобы определить, что $ здесь имеет двоякую природу, и задаться вопросом, что предлагает Perl для ее решения. Я думаю, что я нахожу, что ничего не предусмотрено. Я думал, что qr// должен был помочь, и я не знаю, почему это не так. - person harschware; 23.05.2010

Возможно, модификатор /x может помочь:

m/ ^\[INFO\] $ # Match INFO line
   \n
   ^ (.*?) $ # Collect desired line
   \n 
   ^ \[INFO\] # Match another INFO line
/xms

Я не проверял это, поэтому вам, вероятно, придется отлаживать его. Но я думаю, что это предотвратит интерполяцию символов $ как переменных.

person Ryan C. Thompson    schedule 31.05.2010

Хотя я принял ответ Алана Мура (ответ Райана Томпсона также сослужил бы злую шутку, я мог бы принять только один), я хотел сделать совершенно ясное решение, поскольку оно было как бы скрыто в комментариях и обсуждении. Следующий сценарий Perl демонстрирует, что Perl использует $ для интерполяции переменных, если какой-либо символ предшествует знаку доллара, и что отключение интерполяции позволит рассматривать $ как EOL.

use strict;
use warnings;

my $x = "[INFO]\nxyz\n[INFO]";
if( $x =~ /^\[INFO\]$\n(.*?)$\n\[INFO\]/m ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

if( $x =~ m'^\[INFO\]$\n(.*?)$\n\[INFO\]'m ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

if( $x =~ m/ ^\[INFO\] $ # Match INFO line
\n
^ (.*?) $ # Collect desired line
\n 
^ \[INFO\] # Match another INFO line
/xms ) {
    print "'$1' FOUND\n";
} else {
    print "NO MATCH FOUND\n";
}

Скрипт выдает следующий вывод:

Use of uninitialized value $\ in regexp compilation at t.pl line 5.
Use of uninitialized value $\ in regexp compilation at t.pl line 5.
NO MATCH FOUND
'xyz' FOUND
'xyz' FOUND
person harschware    schedule 02.06.2010
comment
Эти предупреждения, казалось бы, указывают на то, что мой ответ неверен. - person Ryan C. Thompson; 17.06.2010
comment
Нет, они исходят из первого регулярного выражения. тот, который я разместил в вопросе (который выдает предупреждения и не соответствует). Второе и третье регулярное выражение совпадают без предупреждений. - person harschware; 17.06.2010