【问题标题】:get value of all perl regex capture groups获取所有 perl 正则表达式捕获组的值
【发布时间】:2018-04-19 13:34:14
【问题描述】:

问题:我正在编写一个库,该库接收用户提供的正则表达式,其中包含要针对其他输入运行的未知数量的捕获组,并且我想提取连接在一个字符串中的所有捕获组的值(用于在其他地方进一步处理)。

如果事先知道捕获组的数量,这很简单,因为我只是指定它们:

#!/usr/bin/perl -w
my $input = `seq -s" " 100 200`;
my $user_regex = 
 qr/100(.*)103(.*)107(.*)109(.*)111(.*)113(.*)116(.*)120(.*)133(.*)140(.*)145/;

if ($input =~ $user_regex)  { print "$1 $2 $3 $4 $5 $6 $7 $8 $9 $10\n"; }

正确生成(忽略多余的空格):

 101 102   104 105 106   108   110   112   114 115   117 118 119 
 121 122 123 124 125 126 127 128 129 130 131 132   
 134 135 136 137 138 139   141 142 143 144

但是,如果有超过 10 个捕获组,如果我不修改代码,就会丢失数据。由于捕获组的数量未知,目前我在no warnings pragma 下使用数百个手动指定的匹配项(“$1”到“$200”)并希望它足够,但它似乎并不特别干净或 健壮。

理想情况下,我希望像 values %+ 那样对命名捕获组起作用,但对非命名捕获组起作用。在 perl 5.24 中可以吗?或者,您会推荐哪种不那么笨拙的方法来检索所有编号的捕获组的内容?

【问题讨论】:

    标签: regex perl


    【解决方案1】:

    也许你可以直接捕获到一个数组中?

    my @captured = $input =~ $user_regexp;
    if( @captured ) { print join " ", @captured; print "\n"; }
    

    如果您绝对必须使用编号的捕获变量,请使用 eval:

    my $input = "abc";
    my $re = qr/(.)(.)(.)/;
    if( $input =~ $re){
      my $num = 1;
      print "captured \$$num = ". eval("\$$num") ."\n" and $num++
        while eval "defined \$$num";
    }
    

    或者只是:

    my $input = "abc";
    my $re = qr/(.)(.)(.)/;
    if( $input =~ $re){
      my $num = 1;
      print "captured \$$num = $$num\n" and $num++ while defined $$num;
    }
    

    ...但是最后一个带有标量引用的示例在 use strict 下不起作用。

    【讨论】:

    • 谢谢,捕获到组正是我想要的!不敢相信我错过了...“使用严格”确实是必须的,而 eval 似乎更加笨拙...
    • if ( @captured ) 是不够的。正则表达式模式可以匹配而无需填充任何捕获。您需要if ( my @captured = $input =~ $user_regexp ) { print "@captured\n" },即使这样也没有提供undef 的一个或多个捕获,这将引发警告。
    【解决方案2】:

    如果您正在运行 Perl v5.26.2(当前是最新版本)或更高版本,那么您可以使用内置数组 @{^CAPTURE} 而不是自己访问捕获变量

    和普通数组一样,捕获数为scalar @{^CAPTURE},索引从零到$#{^CAPTURE}

    请注意,该数组由最近的成功模式匹配填充,因此就像捕获变量本身一样,您应该在使用@{^CAPTURE}的内容之前检查模式匹配的状态

    【讨论】:

    • perldoc perlvar“这个变量是在 5.25.7 中添加的” 所以这可能是版本的下限。
    【解决方案3】:

    对于 v5.24,没有所有捕获值的数组,但您可以使用每个匹配项的开始/结束位置来提取它们:

    my $s  = <some string>;
    my $re = <some regex with captures>;
    my @matches;
    if ($s =~ $re) {
        for my $i (0 .. $#-) {
            push @matches, substr($s, $-[$i], $+[$i] - $-[$i]);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以将 $1 $2 等中的数字视为变量

      $t="abcdefghijklmnop"; 
      $t=~/(.)(.)(.)(.)(.)(.)(.)/; 
      print $$_ for 1..10;
      

      你可以绕过严格,

        use strict;
        $t="abcdefghijklmnop"; 
        $t=~/(.)(.)(.)(.)(.)(.)(.)/; 
      {
          no strict;
          print $$_ for 1..10;
      }
      

      或者,您可以将它们放在一个数组中(取自http://perldoc.perl.org/perlre.html

      use strict; 
      my $t="abcdefghijklmnop"; 
      my @a=$t=~/(.)(.)(.)(.)(.)(.)(.)/; 
      print "@a";
      

      虽然两者都不完美,但使用严格引用意味着您知道变量的名称。因此,理想情况下,您应该知道变量名称,例如,您使用了多少个捕获组

      【讨论】:

        【解决方案5】:

        Michael CarmanBorodin 提到的变量在 perlvar - http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions 中有帮助。

        也就是说,我将几个帖子中的想法结合在一起,形成了我希望的更全面的答案:

        #!/usr/bin/env perl
        
        use Modern::Perl;
        
        my @a = 'abcde' =~ /(.).(.).(.)/;
        
        say do { # map probably creates a temp anonymous array of capture strings
            no strict 'refs';
            join ' ', map { "$$_" } 1..$#-
        };
        
        say do { # no copy to array but eval
            eval '"' . join(" ", map { "\$$_" } 1..$#-) . '"';
        };
        
        say "@a"; # still not clear from OP why this wasn't the answer
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-09-06
          • 1970-01-01
          • 1970-01-01
          • 2018-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-21
          相关资源
          最近更新 更多