Бесконечный цикл 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
, мы просто печатаем ее сразу.