我可以使用Perl正则表达式来匹配平衡文本吗?

per*_*faq 13 regex perl brackets recursive-regex

我想在Perl中匹配括号中的文本.我怎样才能做到这一点?


这是来自官方perlfaq的问题.我们将perlfaq导入Stack Overflow.

per*_*faq 23

这是官方的FAQ回答减去任何后续编辑.

您的第一次尝试可能应该是Text :: Balanced模块,它位于Perl 5.8之后的Perl标准库中.它具有处理棘手文本的各种功能.在正则表达式::常见模块还可以通过提供可以使用罐装的模式帮助.

从Perl 5.10开始,您可以使用递归模式将平衡文本与正则表达式进行匹配.在Perl 5.10之前,你不得不采用各种技巧,例如在(??{})序列中使用Perl代码.

这是一个使用递归正则表达式的示例.目标是捕获尖括号内的所有文本,包括嵌套尖括号中的文本.此示例文本有两个"主要"组:一个具有一个嵌套级别的组和一个具有两个嵌套级别的组.尖括号中总共有五组:

I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
Run Code Online (Sandbox Code Playgroud)

与平衡文本匹配的正则表达式使用两个新的(to Perl 5.10)正则表达式功能.perlre中包含了这些内容,此示例是该文档中的一个修改版本.

首先,向+任何量词添加新的所有权都会找到最长的匹配并且不会回溯.这很重要,因为你想通过递归来处理任何尖括号,而不是回溯.该组[^<>]++找到一个或多个没有回溯的非尖括号.

其次,new (?PARNO)指的是特定捕获组中的子模式PARNO.在下面的正则表达式中,第一个捕获组找到(并记住)平衡文本,并且您需要在第一个缓冲区内使用相同的模式来通过嵌套文本.这是递归部分.在(?1)使用外捕获组中的图案作为正则表达式的一个独立部分.

把它们放在一起,你有:

#!/usr/local/bin/perl5.10.0

my $string =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE

my @groups = $string =~ m/
        (                   # start of capture group 1
        <                   # match an opening angle bracket
            (?:
                [^<>]++     # one or more non angle brackets, non backtracking
                  |
                (?1)        # found < or >, so recurse to capture group 1
            )*
        >                   # match a closing angle bracket
        )                   # end of capture group 1
        /xg;

$" = "\n\t";
print "Found:\n\t@groups\n";
Run Code Online (Sandbox Code Playgroud)

输出显示Perl找到了两个主要组:

Found:
    <brackets in <nested brackets> >
    <another group <nested once <nested twice> > >
Run Code Online (Sandbox Code Playgroud)

通过一些额外的工作,您可以将所有组都放在尖括号中,即使它们也位于其他尖括号中.每次获得平衡匹配时,删除其外部分隔符(即刚刚匹配的分隔符,因此不再匹配)并将其添加到要处理的字符串队列中.继续这样做,直到你没有匹配:

#!/usr/local/bin/perl5.10.0

my @queue =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE

my $regex = qr/
        (                   # start of bracket 1
        <                   # match an opening angle bracket
            (?:
                [^<>]++     # one or more non angle brackets, non backtracking
                  |
                (?1)        # recurse to bracket 1
            )*
        >                   # match a closing angle bracket
        )                   # end of bracket 1
        /x;

$" = "\n\t";

while( @queue )
    {
    my $string = shift @queue;

    my @groups = $string =~ m/$regex/g;
    print "Found:\n\t@groups\n\n" if @groups;

    unshift @queue, map { s/^<//; s/>$//; $_ } @groups;
    }
Run Code Online (Sandbox Code Playgroud)

输出显示所有组.最外面的匹配首先显示,嵌套匹配稍后显示:

Found:
    <brackets in <nested brackets> >
    <another group <nested once <nested twice> > >

Found:
    <nested brackets>

Found:
    <nested once <nested twice> >

Found:
    <nested twice>
Run Code Online (Sandbox Code Playgroud)