【问题标题】:How can I tell which of the alternatives matched in a Perl regular expression pattern?如何判断 Perl 正则表达式模式中匹配的替代项?
【发布时间】:2011-12-25 12:32:18
【问题描述】:

我有一个正则表达式列表(大约 10 到 15 个),我需要匹配一些文本。在一个循环中一一匹配它们太慢了。但是,我没有编写自己的状态机来一次匹配所有正则表达式,而是尝试| 各个正则表达式并让 perl 完成工作。问题是我怎么知道哪些选项匹配?

这个问题解决了每个单独的正则表达式中没有捕获组的情况。 (which portion is matched by regex?) 如果每个正则表达式中都有捕获组怎么办?

所以有了以下,

/^(A(\d+))|(B(\d+))|(C(\d+))$/

和字符串“A123”,我怎么知道A123匹配并提取“123”?

【问题讨论】:

    标签: regex perl capture regex-group


    【解决方案1】:

    您无需编写自己的状态机来组合正则表达式。查看Regexp:Assemble。它有一些方法可以跟踪你的初始模式匹配。

    编辑:

    use strict;
    use warnings;
    
    use 5.012;
    
    use Regexp::Assemble;
    
    my $string = 'A123';
    
    my $re = Regexp::Assemble->new(track => 1);
    for my $pattern (qw/ A(\d+) B(\d+) C(\d+) /) {
      $re->add($pattern);
    }
    
    say $re->re; ### (?-xism:(?:A(\d+)(?{0})|B(\d+)(?{2})|C(\d+)(?{1})))
    say for $re->match($string); ### A(\d+)
    say for $re->capture; ### 123
    

    【讨论】:

    • 我在模块自述文件的“跟踪”部分找到了正是我需要的东西。 - 我需要发送一个特定的模式匹配。很棒的工具。
    【解决方案2】:

    为什么不使用/^ (?<prefix> A|B|C) (?<digits> \d+) $/x。请注意,命名捕获组是为了清楚起见,并非必不可少。

    【讨论】:

      【解决方案3】:

      A123 将在捕获组 $1 中,123 将在组 $2

      所以你可以说:

      if ( /^(A(\d+))|(B(\d+))|(C(\d+))$/ && $1 eq 'A123' && $2 eq '123' ) {
          ...
      }
      

      这是多余的,但你明白了......

      编辑:不,你不必枚举每个子匹配,你问如何知道A123是否匹配以及如何提取123

      • 除非A123 匹配,否则您不会进入if
      • 您可以使用$2 反向引用提取123

      所以也许这个例子会更清楚:

      if ( /^(A(\d+))|(B(\d+))|(C(\d+))$/ ) {
          # do something with $2, which will be '123' assuming $_ matches /^A123/
      }
      

      编辑 2:

      在 AoA 中捕获匹配项(这是一个不同的问题,但应该这样做):

      #!/usr/bin/perl
      use strict;
      use warnings;
      use Data::Dumper;
      
      my @matches = map { [$1,$2] if /^(?:(A|B|C)(\d+))$/ } <DATA>;
      print Dumper \@matches;
      
      __DATA__
      A123
      B456
      C769
      

      结果:

      $VAR1 = [
                [
                  'A',
                  '123'
                ],
                [
                  'B',
                  '456'
                ],
                [
                  'C',
                  '769'
                ]
              ];
      

      请注意,我修改了您的正则表达式,但从您的评论来看,这似乎就是您想要的...

      【讨论】:

      • 那么我必须手动枚举每个子正则表达式中括号的数量吗?我希望像数组内部的数组一样的东西,比如@groups = ($_ =~ /^(A(\d+))|(B(\d+))|(C(\d+))$/) 和 $ group[1]->[0] 将匹配“123”。不可能?
      • 如果每个子正则表达式中有任意数量的捕获组,那么知道匹配的 $3(例如)将无法帮助我找出匹配的子正则表达式,而无需先弄清楚哪个 $?对应于哪个捕获。
      【解决方案4】:

      使用你的示例数据,很容易编写

      'A123' =~ /^([ABC])(\d+)$/;
      

      $1 将包含前缀,$2 将包含后缀。

      我无法判断这是否与您的真实数据相关,但使用额外的模块似乎有点过头了。

      【讨论】:

      • ABC 123 只是一个例子,我在运行时从其他模块中读取了更复杂的模式。所以使用 Regexp::Assemble 是合理的。
      【解决方案5】:

      您可以在 Perl 中做的另一件事是使用“(?{...})”将 Perl 代码直接嵌入到您的正则表达式中。因此,您可以设置一个变量来告诉您正则表达式的哪一部分匹配。警告:您的正则表达式不应包含任何变量(嵌入的 Perl 代码之外),这些变量将被插入到正则表达式中,否则您将得到错误。这是一个使用此功能的示例解析器:

      my $kind;
      my $REGEX  = qr/
                [A-Za-z][\w]*                        (?{$kind = 'IDENT';})
              | (?: ==? | != | <=? | >=? )           (?{$kind = 'OP';})
              | -?\d+                                (?{$kind = 'INT';})
              | \x27 ( (?:[^\x27] | \x27{2})* ) \x27 (?{$kind = 'STRING';})
              | \S                                   (?{$kind = 'OTHER';})
              /xs;
      
      my $line = "if (x == 'that') then x = -23 and y = 'say ''hi'' for me';";
      my @tokens;
      while ($line =~ /( $REGEX )/xsg) {
          my($match, $str) = ($1,$2);
          if ($kind eq 'STRING') {
              $str =~ s/\x27\x27/\x27/g;
              push(@tokens, ['STRING', $str]);
              }
          else {
              push(@tokens, [$kind, $match]);
              }
          }
      foreach my $lItems (@tokens) {
          print("$lItems->[0]: $lItems->[1]\n");
          }
      

      打印出以下内容:

      IDENT: if
      OTHER: (
      IDENT: x
      OP: ==
      STRING: that
      OTHER: )
      IDENT: then
      IDENT: x
      OP: =
      INT: -23
      IDENT: and
      IDENT: y
      OP: =
      STRING: say 'hi' for me
      OTHER: ;
      

      这有点做作,但您会注意到字符串周围的引号(实际上是撇号)被去掉了(而且,连续的引号被折叠成单引号),所以一般来说,只有 $kind 变量会告诉你解析器是否看到了标识符或带引号的字符串。

      【讨论】:

        猜你喜欢
        • 2011-08-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多