Perl正则表达式:匹配嵌套括号

时间:2013-03-08 19:24:28

标签: regex perl

我正在尝试将嵌套的{}括号与Perl中的正则表达式匹配,以便我可以从文件中提取某些文本。这就是我目前所拥有的:

my @matches = $str =~ /\{(?:\{.*\}|[^\{])*\}|\w+/sg;

foreach (@matches) {
    print "$_\n";
}

在某些时候,这可以按预期工作。例如,如果$str = "abc {{xyz} abc} {xyz}"我获得:

abc
{{xyz} abc}
{xyz}

正如所料。但是对于其他输入字符串,它不能按预期运行。例如,如果$str = "{abc} {{xyz}} abc",则输出为:

{abc} {{xyz}}
abc

这不是我的预期。我本来希望{abc}{{xyz}}分开,因为每个都是根据括号自行平衡的。我的正则表达式有问题吗?如果是这样,我将如何解决它?

7 个答案:

答案 0 :(得分:12)

perlfaq5中介绍了匹配平衡分隔符和嵌套分隔符的问题,我将留给它们以涵盖所有选项,包括(?PARNO)Regexp::Common

但是匹配平衡项是棘手的并且容易出错,除非你真的想要学习和维护高级正则表达式,将它留给模块。幸运的是,有Text::Balanced来处理这个问题,所以还有更多。它是平衡文本匹配的瑞士军用链锯。

不幸的是it does not handle escaping on bracketed delimiters

use v5.10;
use strict;
use warnings;

use Text::Balanced qw(extract_multiple extract_bracketed);

my @strings = ("abc {{xyz} abc} {xyz}", "{abc} {{xyz}} abc");

for my $string (@strings) {
    say "Extracting from $string";

    # Extract all the fields, rather than one at a time.
    my @fields = extract_multiple(
        $string,
        [
            # Extract {...}
            sub { extract_bracketed($_[0], '{}') },
            # Also extract any other non whitespace
            qr/\S+/
        ],
        # Return all the fields
        undef,
        # Throw out anything which does not match
        1
    );

    say join "\n", @fields;
    print "\n";
}

您可以将extract_multiple视为更通用且更强大的split

答案 1 :(得分:12)

你对你的模式如何匹配感到惊讶,但没有人解释过它?以下是您的模式匹配方式:

my @matches = $str =~ /\{(?:\{.*\}|[^{])*\}|\w+/sg;
                       ^    ^ ^ ^  ^      ^
                       |    | | |  |      |
{ ---------------------+    | | |  |      |
a --------------------------)-)-)--+      |
b --------------------------)-)-)--+      |
c --------------------------)-)-)--+      |
} --------------------------)-)-)--+      |
  --------------------------)-)-)--+      |
{ --------------------------+ | |         |
{ ----------------------------+ |         |
x ----------------------------+ |         |
y ----------------------------+ |         |
z ----------------------------+ |         |
} ------------------------------+         |
} ----------------------------------------+

正如您所看到的,问题是/ \{.*\} /匹配太多。应该有什么东西匹配

(?: \s* (?: \{ ... \} | \w+ ) )*

...

(?: \s* (?: \{ ... \} | \w+ ) )*

所以你需要一些递归。命名组是一种简单的方法。

say $1
   while /
      \G \s*+ ( (?&WORD) | (?&BRACKETED) )

      (?(DEFINE)
         (?<WORD>      \s* \w+ )
         (?<BRACKETED> \s* \{ (?&TEXT)? \s* \} )
         (?<TEXT>      (?: (?&WORD) | (?&BRACKETED) )+ )
      )
   /xg;

但是为什么不使用Text::Balanced而不是重新发明轮子。

答案 2 :(得分:5)

你需要一个递归的正则表达式。这应该有效:

my @matches;
push @matches, $1 while $str =~ /( [^{}\s]+ | ( \{ (?: [^{}]+ | (?2) )* \} ) )/xg;

或者,如果您更喜欢非循环版本:

my @matches = $str =~ /[^{}\s]+ | \{ (?: (?R) | [^{}]+ )+ \} /gx;

答案 3 :(得分:4)

在每个嵌套级别匹配嵌套括号,只有一对,
但是任何数量的级别,例如{1{2{3}}},您可以使用

/\{[^}]*[^{]*\}|\w+/g

要匹配任何嵌套级别可能有多对,例如{1{2}{2}{2}},您可以使用

/(?>\{(?:[^{}]*|(?R))*\})|\w+/g

(?R)用于递归匹配整个模式。

要匹配一对括号中包含的文字,引擎必须与(?:[^{}]*|(?R))*
匹配 即,[^{}]*(?R),是*的零次或多次。

所以在例如"{abc {def}}",在匹配开头"{"后,[^{}]*将与"abc "匹配,(?R)将与"{def}"匹配,然后结束"}"将匹配。

"{def}"匹配,因为(?R)只是整个模式的缩写 (?>\{(?:[^{}]*|(?R))*\})|\w+,我们刚才看到的内容会匹配"{",后跟文字匹配[^{}]*,后跟"}"

原子分组(?> ... )用于防止正则表达式引擎在匹配后回溯到括号内的文本。这对于确保正则表达式无法快速失败非常重要。

答案 4 :(得分:3)

哇。对于那些简单的事情,有一堆复杂的答案。

你遇到的问题是你在贪婪模式下匹配。也就是说,你正在使正则表达式引擎尽可能地匹配,同时使表达式成为真。

为避免贪婪匹配,只需添加'?'量词之后。这使得比赛尽可能短。

所以,我改变了你的表达方式:

my @matches = $str =~ /\{(?:\{.*\}|[^\{])*\}|\w+/sg;

要:

my @matches = $str =~ /\{(?:\{.*?\}|[^\{])*?\}|\w+/sg;

......现在它完全符合您的预期。

HTH

旧金山

答案 5 :(得分:1)

只需修改并扩展classic solution

(\{(?:(?1)|[^{}]*+)++\})|[^{}\s]++

Demo(这是在PCRE中。当涉及递归正则表达式时,行为与Perl略有不同,但我认为它应该在这种情况下产生相同的结果。)

经过一番努力(我不熟悉Perl!),这是ideone上的演示。 $&指的是整个正则表达式匹配的字符串。

my $str = "abc {{xyz} abc} {xyz} {abc} {{xyz}} abc";

while ($str =~ /(\{(?:(?1)|[^{}]*+)++\})|[^{}\s]++/g) {
    print "$&\n"
}

请注意,此解决方案假定输入有效。它会在无效输入上表现得相当随意。当遇到无效输入时,可以稍微修改它以停止。为此,我需要有关输入格式的更多详细信息(最好是作为语法),例如abc{xyz}asd是否被视为有效输入。

答案 6 :(得分:1)

使用内置模块Text::Balanced的一种方式。

script.pl的内容:

#!/usr/bin/env perl

use warnings;
use strict;
use Text::Balanced qw<extract_bracketed>;

while ( <DATA> ) { 

    ## Remove '\n' from input string.
    chomp;

    printf qq|%s\n|, $_; 
    print "=" x 20, "\n";


    ## Extract all characters just before first curly bracket.
    my @str_parts = extract_bracketed( $_, '{}', '[^{}]*' );

    if ( $str_parts[2] ) { 
        printf qq|%s\n|, $str_parts[2];
    }   

    my $str_without_prefix = "@str_parts[0,1]";


    ## Extract data of balanced curly brackets, remove leading and trailing
    ## spaces and print.
    while ( my $match = extract_bracketed( $str_without_prefix, '{}' ) ) { 
        $match =~ s/^\s+//;
        $match =~ s/\s+$//;
        printf qq|%s\n|, $match;

    }   

    print "\n";
}

__DATA__
abc {{xyz} abc} {xyz}
{abc} {{xyz}} abc

像以下一样运行:

perl script.pl

产量:

abc {{xyz} abc} {xyz}
====================
abc 
{{xyz} abc}
{xyz}

{abc} {{xyz}} abc
====================
{abc}
{{xyz}}