如何使任意Perl正则表达式完全不捕获? (答案:你不能)

时间:2010-08-24 01:02:02

标签: regex perl

如何从Perl正则表达式字符串中的任意嵌套子组中删除捕获?我想将任何正则表达式嵌入到一个包络表达式中,该表达式将子正则表达式作为整个实体以及静态已知的后续组捕获。我是否需要手动将正则表达式字符串转换为使用所有非捕获(?:)组(并希望我不会搞砸),或者是否有提供此功能的Perl正则表达式或库机制?

# How do I 'flatten' $regex to protect $2 and $3?
# Searching 'ABCfooDE' for 'foo' OK, but '((B|(C))fo(o)?(?:D|d)?)', etc., breaks.
# I.E., how would I turn it effectively into '(?:(?:B|(?:C))fo(?:o)?(?:D|d)?)'?
sub check {
  my($line, $regex) = @_;
  if ($line =~ /(^.*)($regex)(.*$)/) {
    print "<", $1, "><", $2, "><", $3, ">\n";
  }
}

附录:我模糊地了解$&$`$',并建议尽可能避免使用它们,我不会可以在我的Perl 5.8环境中访问${^PREMATCH}${^MATCH}${^POSTMATCH}。上面的示例可以使用这些方法划分为2/3块,更复杂的实际案例可以手动迭代这个,但我想如果可能的话我想要一个通用的解决方案。

接受的答案:我希望存在并且令人惊讶(至少对我而言)不是,是一个封装组,使其内容不透明,以便随后的位置反向引用将内容视为单个实体和名称引用是取消范围的。对于Perl 5.10+,gbacon有一个可能有用的解决方法,FM为在特定情况下可以实现相同效果的任何版本显示手动迭代机制,但j_random_hacker将其称为没有真正的语言机制来封装子表达式。

6 个答案:

答案 0 :(得分:8)

一般来说,你不能。

即使您可以将所有(...)转换为(?:...) s,这在一般情况下也不起作用,因为模式可能需要反向引用/(.)X\1/,匹配任何字符,后跟X,后跟最初匹配的字符。

因此,如果没有Perl机制来“在事后”丢弃捕获的结果,则无法解决所有正则表达式的问题。您可以做的最好(或者如果你有Perl 5.10就可以做)是使用gbacon's suggestion并希望为捕获缓冲区生成一个唯一的名称。

答案 1 :(得分:7)

保护您关心的子模式的一种方法是使用named capture buffers

  

此外,从Perl 5.10.0开始,您可以使用命名捕获缓冲区和命名反向引用。表示法是(?<name>...)来声明和\k<name>来引用。您也可以使用撇号而不是尖括号来分隔名称;并且您可以使用括号\g{name}反向引用语法。也可以通过绝对和相对数量来引用命名的捕获缓冲区。在模式之外,可通过%+哈希获得命名的捕获缓冲区。当同一模式中的不同缓冲区具有相同名称时,$+{name}\k<name>将引用最左侧定义的组。

在您的问题中,check变为

sub check {
  use 5.10.0;  
  my($line, $regex) = @_;
  if ($line =~ /(^.*)($regex)(.*$)/) {
    print "<", $+{one}, "><", $+{two}, "><", $+{three}, ">\n";
  }
}

然后用

调用它
my $pat = qr/(?<one>(?<two>B|(?<three>C))fo(o)?(?:D|d)?)/;   
check "ABCfooDE", $pat;

输出

<CfooD><C><C>

答案 2 :(得分:5)

这不能解决一般情况,但您可以使用标量上下文中的/g选项处理您的具体示例,这样您就可以将问题分成两个匹配,第二个匹配在第一个左边的位置关:

sub check {
    my($line, $regex) = @_;
    my ($left_side, $regex_match) = ($1, $2) if $line =~ /(^.*)($regex)/g;
    my $right_side = $1 if $line =~ /(.*$)/g;
    print "<$left_side> <$regex_match> <$right_side>\n"; # <AB> <CfooD> <E123>
}

check( 'ABCfooDE123', qr/((B|(C))fo(o)?(?:D|d)?)/ );

答案 3 :(得分:2)

如果你需要的只是匹配前后字符串的一部分,你可以使用@-@+数组来获得匹配字符串的偏移量:

sub check {
    my ($line, $regex) = @_;
    if ($line =~ /$regex/) {
        my $pre   = substr $line, 0, $-[0];
        my $match = substr $line, $-[0], $+[0] - $-[0];
        my $post  = substr $line, $+[0];
        print "<$pre><$match><$post>\n";
    }
}

答案 4 :(得分:1)

版本的Perl&gt;据报道,5.22有一个'/ n'修饰符可以关闭所有捕获。

答案 5 :(得分:0)

这不会禁用捕获,但可能会达到您想要的效果:

$ perl -wle 'my $_ = "123abc"; /(\d+)/ && print "num: $1"; { /([a-z]+)/ && print "letter: $1"; } print "num: $1";'
num: 123
letter: abc
num: 123

您创建了一个新范围,其外部的$ 1不会受到影响。