合并两个捕获组数量可变的正则表达式

问题描述 投票:0回答:4

我正在努力匹配其中一个

(\S+)(=)([fisuo])

(\S+)(!)

然后将结果放在列表中(捕获组)。我所有的尝试都会导致额外的、不需要的捕获。

这是一些代码:

#!/usr/bin/perl
#-*- cperl -*-
# $Id: test7,v 1.1 2023/04/10 02:57:12 bennett Exp bennett $
#

use strict;
use warnings;
use Data::Dumper;

foreach my $k ('debugFlags=s', 'verbose!') {
    my @v;

    # Below is the offensive looking code.  I was hoping for a regex
    # which would behave like this:

    if(@v = $k =~ m/^(\S+)(=)([fisuo])$/) {
      printf STDERR ("clownMatch = '$k' => %s\n\n", Dumper(\@v));
    } elsif(@v = $k =~ m/^(\S+)(!)$/) {
      printf STDERR ("clownMatch = '$k' => %s\n\n", Dumper(\@v));
    }

    @v = ();

    # This is one of my failed, aspirational matches.  I think I know
    # WHY it fails, but I don't know how to fix it.
    
    if(@v = $k =~ m/^(?:(\S+)(=)([fisuo]))|(?:(\S+)(!))$/) {
      printf STDERR ("hopefulMatch = '$k' => %s\n\n", Dumper(\@v));
    }
    printf STDERR "===\n";
}

exit(0);
__END__

输出:

clownMatch = 'debugFlags=s' => $VAR1 = [
          'debugFlags',
          '=',
          's'
        ];


hopefulMatch = 'debugFlags=s' => $VAR1 = [
          'debugFlags',
          '=',
          's',
          undef,
          undef
        ];


===
clownMatch = 'verbose!' => $VAR1 = [
          'verbose',
          '!'
        ];


hopefulMatch = 'verbose!' => $VAR1 = [
          undef,
          undef,
          undef,
          'verbose',
          '!'
        ];


===

代码注释中有更多详细信息。输出位于代码部分的底部。还有'!'性格就是这样。我没有将它与其他一些东西混淆。

regex perl regex-group
4个回答
3
投票

在交替中,all 捕获的值被返回,即使是那些不匹配的。

一个简单的方法是从返回列表中过滤掉

undef

if (my @v = grep { defined } $s =~ /^(?: (\S+)(=)([fisuo]) | (\S+)(!) )$/x)

还有其他方法可以构建正则表达式,但直接交替就可以了。


3
投票

我们可以使用以下单个正则表达式模式:

^(\S+)([!=])((?<==)[fisuo])?$

这表示匹配:

  • ^
    从字符串的开头
  • (\S+)
    匹配并捕获
    $1
    一个非空白术语
  • ([!=])
    $2
    中匹配并捕获
    !
    =
  • ((?<==)[fisuo])?
    然后在
    $3
    中选择性地捕获来自
    fisuo
    的一封信 后视
    (?<==)
    确保这只匹配
    =
  • $
    字符串的结尾

演示


2
投票

因为你要匹配两个不同的东西,所以有两个不同的匹配似乎是完全合理的。

但是,如果你想把它们结合起来,你可以这样做:

m{^
  (\S+)
  (?:
    =([fisuo]) |
    (!)
  )
  $
}x

$1 是名字。 $2 是开关,如果存在的话。 $3 是 !,如果存在的话。

对于任何更复杂的事情,使用named capturesRegexp::Assemble.

示范


2
投票

我所有的尝试都会导致额外的、不需要的捕获。

我会去“分支重置”

(?| pattern1 | pattern2 | ... )
就像@bobble_bubble已经建议的那样(仅作为评论)

这是一个通用的解决方案,可以将不同的模式与组结合起来,同时重置捕获计数。

唉,与他链接到的文档相反,您仍然会在 LIST 的末尾获得

undef
插槽,用于返回具有较少组的模式。

但是如果这真的困扰你 - 我个人会保留它们 - 你可以像@zdim建议的那样使用

grep {defined}
安全地过滤掉它们。

这是安全的,因为

undef
表示不匹配,不能与空匹配混淆
""
.

这里是覆盖你的测试用例的代码

use v5.12.0;
use warnings;
use Data::Dump qw/pp ddx/;
use Test::More;

# https://stackoverflow.com/questions/75974097/merge-two-regexes-with-variable-number-of-capture-groups

my %wanted =
  (
   "debugFlags=s" => ["debugFlags", "=", "s"],
   "verbose!"     => ["verbose", "!"],
  );


while ( my ( $str, $expect) = each %wanted ) {
    my @got =
      $str =~ / (\S+)
                (?|
                    (=) ([fisuo]+)
                |
                    (!)
                )
              /x;

    ddx \@got;                          # with trailing undefs

    @got = grep {defined} @got;         # eliminate undefs

    is_deeply( \@got, $expect, "$str => ". pp(\@got));
}

done_testing();

-->

# branchreset.pl:25: ["debugFlags", "=", "s"]
ok 1 - debugFlags=s => ["debugFlags", "=", "s"]
# branchreset.pl:25: ["verbose", "!", undef]
ok 2 - verbose! => ["verbose", "!"]
1..2

战略更新

但同样,我没有看到最后消除

undef
插槽的意义,因为无论如何您都需要单独处理不同的情况。

有一天,您可能也想在分支之后添加模式。如果分支重置真的跳过了缺失的组,那将改变尾随组的编号以致面目全非。所以从设计的角度来看做得很好。

© www.soinside.com 2019 - 2024. All rights reserved.