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

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

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/;

Спасибо!!

Ответ 1

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

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

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

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

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

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

Ответ 2

В скалярном контексте регулярное выражение с модификатором /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;
}

Ответ 3

Учебник по регулярным выражениям Perl говорит:

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

Но:

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

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

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

Скажем, у вас есть файл под названием lines.txt:

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

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

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

При запуске как

$ perl match.pl < lines.txt

вы получаете

abcdef
ghijk
lmnop
qrstu
vwxyz

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

$perl -nle 'print $1, если $_ = ~/regexline(.+?)end/' < lines.txt ABCDEF ghijk lmnop qrstu VWXYZ

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

Ответ 6

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

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

Ответ 7

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

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, мы просто печатаем ее сразу.