Бесконечный цикл while в Perl - PullRequest
       0

Бесконечный цикл while в Perl

10 голосов
/ 10 сентября 2011

Есть ли способ сделать это без получения бесконечного цикла?

while((my $var) = $string =~ /regexline(.+?)end/g) {
    print $var;
}

Это приводит к бесконечному циклу, возможно потому, что присвоение переменной var напрямую из регулярного выражения в while каждый раз возвращает "true"?

Я знаю, что могу сделать это:

while($string =~ /regexline(.+?)end/g) {
     my $var = $1;      
     print $var;
}

Но я надеялся, что смогу спасти линию. Можно ли использовать модификатор regex или что-то в этом роде?

(Кроме того, как на самом деле называется эта запись / трюк, если я хочу ее найти:

(my $var) = $string =~ /regex/;

Спасибо !!

Ответы [ 7 ]

10 голосов
/ 10 сентября 2011

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

print "$1\n" while "abacadae" =~ /(a\w)/g;     # produces "ab","ac","ad","ae"

С присваиванием внутри выражения while вы оцениваете свое регулярное выражение в контексте списка. Теперь ваше регулярное выражение больше не действует как итератор, оно просто возвращает список совпадений. Если список не пуст, он принимает истинное значение:

print "$1\n" while () = "abacadae" =~ /(a\w)/g;   # infinite "ae"

Чтобы это исправить, вы можете взять присвоение из оператора while и использовать встроенную переменную $1, чтобы сделать присвоение внутри цикла?

while ($string =~ /regexline(.+?)end/g) {
    my $var = $1;
    print $var;
}
8 голосов
/ 11 сентября 2011

Есть ли способ сделать это без получения бесконечного цикла?

Да. Используйте foreach () вместо цикла while ():

foreach my $var ($string =~ /regexline(.+?)end/g) {

как на самом деле называется эта запись / трюк, если я хочу ее найти

Это называется совпадением в контексте списка. Это описано в "perldoc perlop":

Модификатор g определяет глобальное сопоставление с образцом, то есть сопоставление столько раз, сколько возможно в пределах строки. Как это ведет себя, зависит от контекста. В контексте списка ...

8 голосов
/ 11 сентября 2011

В руководстве по регулярным выражениям Perl написано:

В скалярном контексте последовательные вызовы строки будут // g переходить от совпадения к совпадению, отслеживая положение в строке по мере ее продвижения.

Но:

В контексте списка // g возвращает список совпадающих группировок или, если групп нет, список совпадений для всего регулярного выражения.

То есть в контексте списка //g возвращает массив всех ваших захваченных совпадений сразу (из которых вы впоследствии отбрасываете все, кроме первого), а затем делает это снова и снова каждый раз, когда выполняется ваш цикл (т.е. навсегда).

Так что вы не можете использовать назначение контекста списка в условии цикла, потому что оно не делает то, что вы хотите.

Если вы настаиваете на использовании контекста списка, вы можете сделать это вместо этого:

foreach my $var ($string =~ /regexline(.+?)end/g) {
    print $var;
}
1 голос
/ 11 сентября 2011

Это единственное обстоятельство, когда вы не можете избежать использования глобальных переменных без изменения поведения.

while ($string =~ /regexline(.+?)end/g) {
    my $var = $1;
    ...
}

Если у вас есть только один захват, вы можете избежать использования глобальных переменных, найдя всесовпадения сразу.

for my $var ($string =~ /regexline(.+?)end/g) {
    ...
}

Дополнительная стоимость второй версии обычно незначительна.

0 голосов
/ 11 сентября 2011

Я не знаю, что вы намереваетесь сделать с этим принтом, но это хороший способ сделать это:

say for $string =~ /regex(.+?)end/g;

Формат for (такой же, как у foreach) расширяет совпадение регулярных выражений в списокиз групп захвата, и печатает их.Работает так:

@matches = $string =~ /regex(.+?)end/g;
say for (@matches);

while несколько отличается.Поскольку он использует скалярный контекст, он не загружает группы захвата в память.

say $1 while $string =~ /regex(.+?)end/g;

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

0 голосов
/ 11 сентября 2011

Я думаю, что вам лучше всего заменить строку $ в цикле ... так:

while((my $var) = $string =~ /regexline(.+?)end/g) {
  $string =~ s/$var//;
  print $var . "\n";
}
0 голосов
/ 10 сентября 2011

Есть несколько способов сделать это с меньшим количеством кода.

Допустим, у вас есть файл с именем lines.txt:

regexlineabcdefend
regexlineghijkend
regexlinelmnopend
regexlineqrstuend
This line does not match
Neither does this
regexlinevwxyzend

, и вы хотите извлечь фрагменты, соответствующие вашемурегулярное выражение, то есть фрагменты линии между "регулярным выражением" и "концом".Простой сценарий Perl:

while (<STDIN>) {
    print "$1\n" if $_ =~ /regexline(.+?)end/
}

При таком запуске

$ perl match.pl < lines.txt

вы получаете

abcdef
ghijk
lmnop
qrstu
vwxyz

Вы можете даже сделать все это из командной строки!

$ perl -nle 'напечатать $ 1, если $ _ = ~ /regexline(.+?)end/'

Что касается вашего второго вопросаЯ не уверен, что является специальным названием Perl для этого трюка.

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