【问题标题】:Infinite while-loop in perlperl中的无限while循环
【发布时间】:2011-09-29 09:45:32
【问题描述】:

有没有办法在不陷入无限循环的情况下做到这一点?

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

这会导致无限循环,可能是因为直接从 while 内的正则表达式分配 var 每次都返回“true”?

我知道我能做到:

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

但我希望我能节省一行。有没有我可以使用的正则表达式修饰符或类似的东西?

(另外,这个符号/技巧实际上叫什么,如果我想搜索它:

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

谢谢!!

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    在标量上下文中,带有/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;
    }
    

    【讨论】:

      【解决方案2】:

      有没有办法在不陷入无限循环的情况下做到这一点?

      是的。使用 foreach() 而不是 while() 循环:

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

      这个符号/技巧实际上叫什么,如果我想搜索它

      在列表上下文中称为匹配。在“perldoc perlop”中有描述:

      g 修饰符指定全局模式匹配——即在字符串中匹配尽可能多的次数。它的行为方式取决于上下文。在列表上下文中...

      【讨论】:

      • 请注意 foreach 会将整个结果集加载到内存中,而不是像 while 那样对其进行迭代。
      【解决方案3】:

      Perl regular expressions tutorial 说:

      在标量上下文中,对字符串的连续调用将 //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 脚本是:

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

          当这样运行时

          $ perl match.pl < lines.txt
          

          你得到

          abcdef
          ghijk
          lmnop
          qrstu
          vwxyz
          

          您甚至可以在命令行上完成所有操作!

          $ perl -nle 'print $1 if $_ =~ /regexline(.+?)end/'

          就您的第二个问题而言,我不确定 是否有 用于该技巧的特殊 Perl 名称。

          【讨论】:

            【解决方案6】:

            我认为你最好的选择是在循环中替换 $string...所以:

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

            【讨论】:

            • 我建议您尝试运行该代码;它有语法错误。 print $var . "\n"; 可以,但print "$var\n"; 更干净。而且你不需要在$string 上做另一个替换;您可以在初始正则表达式中捕获新值并将其分配给$string。
            【解决方案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,我们只需立即打印它。

            【讨论】:

              猜你喜欢
              • 2014-03-29
              • 2012-12-24
              • 2015-06-25
              • 2013-02-22
              • 2011-11-19
              • 1970-01-01
              • 2012-09-10
              • 1970-01-01
              • 2012-08-28
              相关资源
              最近更新 更多