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

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

[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 с $ в моем шаблоне? Как получить регулярное выражение для компиляции?

Ответы [ 5 ]

4 голосов
/ 21 мая 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' );
4 голосов
/ 21 мая 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";

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

4 голосов
/ 20 мая 2010

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

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

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

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

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

1 голос
/ 02 июня 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
1 голос
/ 01 июня 2010

Может быть, модификатор /x может помочь:

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

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

...