我正在尝试将嵌套的{}
括号与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}}
分开,因为每个都是根据括号自行平衡的。我的正则表达式有问题吗?如果是这样,我将如何解决它?
答案 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}}