在Perl中如何提取匹配花括号之间的字符串?

12

我的输入文件如下:

HEADER 
{ABC|*|DEF {GHI 0 1 0} {{Points {}}}}

{ABC|*|DEF {GHI 0 2 0} {{Points {}}}}

{ABC|*|XYZ:abc:def {GHI 0 22 0} {{Points {{F1 1.1} {F2 1.2} {F3 1.3} {F4 1.4}}}}}

{ABC|*|XYZ:ghi:jkl {JKL 0 372 0} {{Points {}}}}

{ABC|*|XYZ:mno:pqr {GHI 0 34 0} {{Points {}}}}

{
    ABC|*|XYZ:abc:pqr {GHI 0 68 0}
        {{Points {{F1 11.11} {F2 12.10} {F3 14.11} {F4 16.23}}}}
        }
TRAILER

我想将文件提取到一个数组中,如下:

$array[0] = "{ABC|*|DEF {GHI 0 1 0} {{Points {}}}}"

$array[1] = "{ABC|*|DEF {GHI 0 2 0} {{Points {}}}}"

$array[2] = "{ABC|*|XYZ:abc:def {GHI 0 22 0} {{Points {{F1 1.1} {F2 1.2} {F3 1.3} {F4 1.4}}}}}"

..
..

$array[5] = "{
    ABC|*|XYZ:abc:pqr {GHI 0 68 0}
        {{Points {{F1 11.11} {F2 12.10} {F3 14.11} {F4 16.23}}}}
        }"
这意味着我需要将第一个左大括号与其匹配的右大括号匹配,并提取它们之间的字符串。
我已经查看了下面的链接,但这不适用于我的问题。 Regex to get string between curly braces "{I want what's between the curly braces}" 我正在尝试,如果有人能提供帮助,那真的会很有帮助...
谢谢 Sri ...
7个回答

15

谢谢 ysth,这是最好的解决方案!! - Srilesh
@Srilesh:如果您认为这个答案最好,请点击答案左侧的勾选标记。 - Ether

15

这可以在现代版本的Perl中使用正则表达式来实现:

my @array = $str =~ /( \{ (?: [^{}]* | (?0) )* \} )/xg;

print join "\n" => @array;
正则表达式匹配花括号块,该块包含非花括号字符或递归到自身(匹配嵌套的花括号)。
编辑:上述代码适用于Perl 5.10+,对于早期版本,递归的写法略有不同。
my $re; $re = qr/ \{ (?: [^{}]* | (??{$re}) )* \} /x;

my @array = $str =~ /$re/xg;

尝试了这个,但是出现了错误:“Sequence (?0...) not recognized in regex; marked by <-- HERE in m/( { (?: [^{}]* | (?0 <-- HERE ) )* } )/”。 - Srilesh
@Srilesh => 我发布的代码需要 Perl 5.10+,我已经编辑了我的答案,包括一个适用于旧版本 Perl 的版本。 - Eric Strom
1
@ysth、@Zaid和@leonbloy提供的解决方案对我来说很好,但是@eric的解决方案性能非常出色。我正在一个10MB的文件上应用递归,与其他方法相比,结果真的很快。我选择您的答案作为最佳解决方案。非常感谢。 - Srilesh

5
我赞同 ysth 的建议,使用 Text::Balanced 模块。只需几行代码即可开始。
use strict;
use warnings;
use Text::Balanced qw/extract_multiple extract_bracketed/;

my $file;
open my $fileHandle, '<', 'file.txt';

{ 
  local $/ = undef; # or use File::Slurp
  $file = <$fileHandle>;
}

close $fileHandle;

my @array = extract_multiple(
                               $file,
                               [ sub{extract_bracketed($_[0], '{}')},],
                               undef,
                               1
                            );

print $_,"\n" foreach @array;

输出

{ABC|*|DEF {GHI 0 1 0} {{Points {}}}}
{ABC|*|DEF {GHI 0 2 0} {{Points {}}}}
{ABC|*|XYZ:abc:def {GHI 0 22 0} {{Points {{F1 1.1} {F2 1.2} {F3 1.3} {F4 1.4}}}}}
{ABC|*|XYZ:ghi:jkl {JKL 0 372 0} {{Points {}}}}
{ABC|*|XYZ:mno:pqr {GHI 0 34 0} {{Points {}}}}
{
    ABC|*|XYZ:abc:pqr {GHI 0 68 0}
        {{Points {{F1 11.11} {F2 12.10} {F3 14.11} {F4 16.23}}}}
        }

根据ysth的建议,我使用了Text::Balanced,但是我只得到了第一个匹配项。感谢您在这里的帮助,我需要使用extract_multiple子程序。谢谢。 - Srilesh

2

你总是可以依靠花括号:

my $depth = 0;
my $out = "";
my @list=();
foreach my $fr (split(/([{}])/,$data)) {
    $out .= $fr;
    if($fr eq '{') {
        $depth ++;
    }
    elsif($fr eq '}') {
        $depth --;
        if($depth ==0) {
            $out =~ s/^.*?({.*}).*$/$1/s; # trim
            push @list, $out;
            $out = "";
        }
    }
}
print join("\n==================\n",@list);

这是老旧的,朴素的Perl风格(可能很丑陋)。


2
我认为在这里不应使用纯正则表达式(在我看来,这甚至可能无法使用正则表达式进行解析)。
相反,建立一个类似于这里展示的小型解析器:http://www.perlmonks.org/?node_id=308039 (见2003年11月18日下午6:29 shotguneFX(Parson)的回答)。
更新:似乎可以用正则表达式做到 - 我看到了匹配嵌套括号的参考《精通正则表达式》(可在Google Books上获得,因此如果您没有该书,则可以搜索第5章“匹配平衡的括号集”)。

0
在这种类型的解析中,使用状态机比正则表达式要好得多。

0

正则表达式实际上不太适合匹配大括号。根据你想要的深度,你可以为Parse::RecDescent编写完整的语法(比听起来容易得多!)。或者,如果你只想获取块,可以搜索开头 '{' 标记和结尾 '}',并且在任何给定时间保持计数。


谢谢 Zig,你的回复非常有帮助。 - Srilesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接