【问题标题】:Strange issue with regex matching in perl, alternate attempts matchperl中正则表达式匹配的奇怪问题,替代尝试匹配
【发布时间】:2014-07-22 20:18:41
【问题描述】:

考虑以下 perl 脚本:

 #!/usr/bin/perl

 my $str = 'not-found=1,total-found=63,ignored=2';

 print "1. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
 print "2. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
 print "3. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
 print "4. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);

 print "Bye!\n";

运行后的输出是:

1. matched using regex
3. matched using regex
Bye!

相同的正则表达式匹配一次,之后不会立即匹配。知道为什么在 perl 中使用相同正则表达式匹配相同字符串的替代尝试失败了吗?

谢谢!

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    这里是详细的解释为什么您的代码不起作用。

    /g 修饰符将正则表达式的行为更改为“全局匹配”。这将匹配字符串中所有出现的模式。但是,如何进行这种匹配取决于 context。 Perl 中的两个(主要)上下文是 list context(复数)和 scalar context(单数)。

    列表上下文中,全局正则表达式匹配返回所有匹配子字符串的列表,或所有匹配捕获的平面列表:

    my $_ = "foobaa";
    my $regex = qr/[aeiou]/;
    
    my @matches = /$regex/g; # match all vowels
    say "@matches"; # "o o a a"
    

    标量上下文中,匹配似乎返回一个描述正则表达式是否匹配的 perl 布尔值:

    my $match = /$regex/g;
    say $match; # "1" (on failure: the empty string)
    

    然而,正则表达式变成了一个迭代器。每次执行正则表达式匹配时,正则表达式从字符串中的当前位置开始,并尝试匹配。如果匹配,则返回 true。如果匹配失败,那么

    • 匹配返回 false,并且
    • 字符串中的当前位置设置为开头。

    由于字符串中的位置被重置,下一次匹配将再次成功。

    my $match;
    say $match while $match = /$regex/g;
    say "The match returned false, or the while loop would have go on forever";
    say "But we can match again" if /$regex/g;
    

    第二个效果——重置位置——可以通过附加的/c 标志来取消。

    可以通过pos函数访问字符串中的位置:pos($string)返回当前位置,可以像pos($string) = 0一样设置。

    正则表达式也可以使用\G 断言锚定在当前位置,就像^ 将正则表达式锚定在字符串的开头。

    这种m//gc 风格的匹配使得编写分词器变得容易:

    my @tokens;
    my $_ = "1, abc, 2 ";
    TOKEN: while(pos($_) < length($_)) {
      /\G\s+/gc and next; # skip whitespace
      # if one of the following matches fails, the next token is tried
      if    (/\G(\d+)/gc) { push @tokens, [NUM => $1]}
      elsif (/\G,/gc    ) { push @tokens, ['COMMA'  ]}
      elsif (/\G(\w+)/gc) { push @tokens, [STR => $1]}
      else { last TOKEN } # break the loop only if nothing matched at this position.
    }
    say "[@$_]" for @tokens;
    

    输出:

    [NUM 1]
    [COMMA]
    [STR abc]
    [COMMA]
    [NUM 2]
    

    【讨论】:

      【解决方案2】:

      去掉 mg 作为你的正则表达式的修饰符,他们没有做你想做的事。

      print "1. matched using regex\n" if ($str =~ /total-found=(\d+)/);
      print "2. matched using regex\n" if ($str =~ /total-found=(\d+)/);
      print "3. matched using regex\n" if ($str =~ /total-found=(\d+)/);
      print "4. matched using regex\n" if ($str =~ /total-found=(\d+)/);
      

      具体来说,m 在此上下文中是可选的,m/foo//foo/ 完全相同。真正的问题是g 在这种情况下做了很多你不想要的事情。详情请见perlretut

      【讨论】:

      • m 可以留在图片中。这是多余的,但没有伤害。括号(全部)可以去。
      • 好点@imran - 无需使用捕获括号...我猜在实际代码中 stony_dreams 正在使用该信息!
      • 我想要括号,因为我需要提取数字。从末尾删除'g'标志确实修复了它。我仍然对为什么在使用“g”标志时完全相同的条件会失败感到困惑。知道'g'正在做什么来导致这种行为吗?在我的实际脚本中,'$str' 是一个长的多行字符串,这就是我使用 'g' 标志的原因。
      • @stony_dreams - 阅读文档 - 在此上下文中使用 g 标志可让您通过连续调用 $str =~/...../g 来遍历捕获的匹配项
      • 谢谢,这真的很有帮助,我想要 /m 而不是 /g。
      【解决方案3】:
       my $str = 'not-found=1,total-found=63,ignored=2';
      
       print "1. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
      

      匹配 total-found=63pos($str) 的下一次匹配尝试设置为偏移 26。

       print "2. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
      

      匹配nothing,因此pos($str)被重置为偏移量0。

      这就是为什么

       print "3. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
      

      再次匹配 total-found=63pos($str) 下一次匹配尝试再次设置为偏移 26,这就是原因

       print "4. matched using regex\n" if ($str =~ m/total-found=(\d+)/g);
      

      像第二个一样再次失败,将 pos($str) 重新设置为偏移 0。

       print "Bye!\n";
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-10
        • 1970-01-01
        • 1970-01-01
        • 2011-07-17
        相关资源
        最近更新 更多