如何检测Perl Regexp中有多少个捕获组?

时间:2017-01-19 13:50:25

标签: regex perl

我在脚本中有一堆 个。我想知道它们中有多少个捕获组。更确切地说,我想知道在@ - 和@ +数组中添加了多少项,如果它们匹配之前实际在实际匹配操作中使用它们。

一个例子:

'XXAB(CD)DE\FG\XX' =~ /(?i)x(ab)\(cd\)(?:de)\\(fg\\)x/
    and print "'@-', '@+'\n";

在这种情况下,输出为:

'1 2 11', '15 4 14'

所以匹配后我知道第0项是字符串的匹配部分,并且有两个捕获组表达式。在实际比赛之前是否可以知道?

我试着把注意力集中在开口支架上。所以我删除了' \\'首先使模式更容易检测转义的括号。然后我删除了' \('字符串。然后来了'(?'。现在我可以计算其余的左括号。

my $re = '(?i)x(ab)\(cd\)(?:de)\\\\(fg\\\\)x'; print "ORIG: '$re'\n";
'XXAB(CD)DE\FG\XX' =~ /$re/ and print "RE: '@-', '@+'\n";
$re =~ s/\\\\//g; print "\\\\: '$re'\n";
$re =~ s/\\\(//g; print "\\(: '$re'\n";
$re =~ s/\(\?//g; print "\\?: '$re'\n";
my $n = ($re =~ s/\(//g); print "n=$n\n";

输出:

ORIG: '(?i)x(ab)\(cd\)(?:de)\\(fg\\)x'
RE: '1 2 11', '15 4 14'
\\: '(?i)x(ab)\(cd\)(?:de)(fg)x'
\(: '(?i)x(ab)cd\)(?:de)(fg)x'
\?: 'i)x(ab)cd\):de)(fg)x'
n=2

所以我知道这个中有2个捕获组。但也许有一种更简单的方法,这绝对不完整(例如,这会将(?<foo>...)(?'foo'...)视为非限制性群体。)

另一种方法是转储regcomp函数的内部数据结构。也许包Regexp::Debugger可以解决问题,但我没有权利在我的环境中安装包。

实际上是某些ARRAY引用的关键,我想在实际应用之前检查引用的ARRAY是否包含适当数量的值。当然,这种检查可以在模式匹配之后立即完成,但如果我可以在脚本的加载阶段进行检查,那就更好了。

提前感谢您的帮助和意见!

3 个答案:

答案 0 :(得分:1)

正则表达式:

\\.(*SKIP)(?!)|\((?(?=\?)\?(P?['<]\w+['>]))

说明:

\\.                     # Match any escaped character
(*SKIP)(?!)             # Discard it
|                       # OR
\(                      # Match a single `(`
(?(?=\?)                # Which if is followed by `?`
    \?                      # Match `?`
    P?['<]\w+['>]           # Next characters should be matched as ?P'name', ?<name> or ?'name'
)                       # End of conditional statement

的Perl:

my @offsets = ();
while ('XXAB(CD)DE\FG\X(X)' =~ /\\.(*SKIP)(?!)|\((?(?=\?)\?(P?['<]\w+['>]))/g){
    push @offsets, "$-[0]";
}
print join(", ", @offsets);

输出:

4, 15

表示输入字符串中存在两个捕获组。

答案 1 :(得分:1)

对于发生的正则表达没有任何限制要求,我认为对捕获组的数量没有明确的答案。只需考虑具有不同捕获组计数的替代方案以及在每个分支中再次发生这种情况的可能性:

my $re = qr/ A(B)C | A(D|(E(G+|H))F /x;

这个正则表达式显然最多可以有3个捕获组。你可以递归地解析每个分支,并取最高的数字作为结果 - 但老实说,我不能在短时间内提出一个实用的方法。对于线性&#39;正则表达式不使用替代或非基本的正则表达式功能,确定捕获组计数的任务是可能的,但我不认为它与更高级的一样可行。

答案 2 :(得分:0)

正如Mr. Obama所说:“是的,我们可以!”

我找到了一个解决方案,它不需要额外的模块并处理所有可能的捕获组事件(据我所知)。正如池上提到它需要重新表达正则表达式,但为我们做了。

在CPAN上挖掘Perl模块的大海草时,我找到了一个非常有趣的名为warnings::regex::recompile的人。每次重新编译正则表达式时,它都会生成警告消息。分析来源我找到了问题的解决方案。

使用use re qw/Debug DUMP/; Perl将解析后的正则表达式返回给STDERR。在原始模块中,结果被转储到真实文件,然后重新读取以进行处理。我修改了代码以使用内存文件。

我的解决方案是:

sub dumpre {
  use re qw(eval Debug DUMP);
  my $buf = ''; 

  open OLDERR, '>&', STDERR or die "$!";
  close STDERR or die "$!";
  open STDERR, '>', \$buf or die "$!";

  my $re = qr/$_[0]/;

  close STDERR or die "$!";
  open STDERR, '>&', OLDERR or die "$!";
  close OLDERR or die "$!";

  no re 'debug'; # Needed because of split

  return [ split '\n', $buf ];
}

此函数在编译正则表达式时打开DUMP。使eval能够处理(?{...})(??{...})表达式。

my $re = 'aa(?:(a\d)+x)?((b\d)*d)*c*(d\d)?(e*)((f)+)(g)+';
my $r = dumpre $re;
print join "\n", @$r;

结果是:

Compiling REx "aa(?:(a\d)+x)?((b\d)*d)*c*(d\d)?(e*)((f)+)(g)+"
Final program:
   1: EXACT <aa> (3)
   3: CURLYX[0] {0,1} (19)
   5:   CURLYM[1] {1,32767} (16)
   9:     EXACT <a> (11)
  11:     POSIXU[\d] (14)
  14:     SUCCEED (0)
  15:   NOTHING (16)
  16:   EXACT <x> (18)
  18: WHILEM (0)
  19: NOTHING (20)
  20: CURLYX[1] {0,32767} (40)
  22:   OPEN2 (24)
  24:     CURLYM[3] {0,32767} (35)
  28:       EXACT <b> (30)
  30:       POSIXU[\d] (33)
  33:       SUCCEED (0)
  34:     NOTHING (35)
  35:     EXACT <d> (37)
  37:   CLOSE2 (39)
  39: WHILEM[1/7] (0)
  40: NOTHING (41)
  41: STAR (44)
  42:   EXACT <c> (0)
  44: CURLYM[4] {0,1} (55)
  48:   EXACT <d> (50)
  50:   POSIXU[\d] (53)
  53:   SUCCEED (0)
  54: NOTHING (55)
  55: OPEN5 (57)
  57:   STAR (60)
  58:     EXACT <e> (0)
  60: CLOSE5 (62)
  62: OPEN6 (64)
  64:   CURLYN[7] {1,32767} (74)
  66:     NOTHING (68)
  68:     EXACT <f> (0)
  72:   WHILEM (0)
  73:   NOTHING (74)
  74: CLOSE6 (76)
  76: CURLYN[8] {1,32767} (86)
  78:   NOTHING (80)
  80:   EXACT <g> (0)
  84: WHILEM (0)
  85: NOTHING (86)
  86: END (0)
anchored "aa" at 0 floating "fg" at 2..9223372036854775807 (checking floating) minlen 4 

因此OPEN\d+CURLYM[\d+]CURLYN[\d+]的行显示捕获括号表达式(行语法:segment_no:regex命令(下一段))。 (注意:CURLYX是一个非捕获括号表达式,如(?:...)+)。 OPEN / CURLY [MN}之后的数字显示捕获组的序数。必须找到最后一个。在这种情况下,这是8。

不幸的是,如果(??{...})返回括号表达式,它就无法处理,但现在我并不需要这样做。 我认为格式不是固定的,因此它可能因版本而异。但对我来说没问题。