使用正则表达式匹配double char分隔的字符串

时间:2010-07-12 20:33:17

标签: regex perl

假设您要匹配由双字符分隔的文本,如下所示:

a = <<
Hello
World!
>>

正则表达式/<<(.*)>>/似乎会这样做,但不幸的是,当这些可以重复时,贪婪的匹配变得太多了:

a = <<
Hello
World!
>>

b = <<
Goodbye
World!
>>

之前的正则表达式将捕获

Hello
World!
>>

b = <<
Goodbye
World!

显而易见的答案是让regexp非贪婪:/&lt;&lt;(。*?)&gt;&gt; /

不幸的是,这对于长字符串(至少在Perl中)存在极端的性能问题。如果分隔符是单个字符,那么我们可以使用字符类(除了字符之外的所有字符)来解决贪婪问题。

关于正则表达式的任何想法,以使这种匹配没有性能损失?

注意:我必须使用Perl,这必须是一个正则表达式,因为它嵌入了更大的系统。

感谢。

5 个答案:

答案 0 :(得分:4)

扩展drewk的答案,以便它确实有效:

/<<((?:(?>[^>]+)|>(?!>))*)>>/

匹配“&lt;&lt;”,然后是0个或更多个块的序列,这些块是任意数量的非“&gt;”字符或单个“&gt;”没有跟着另一个“&gt;”,最后是“&gt;&gt;”。

答案 1 :(得分:3)

您使用的是Perl 5.10吗?试试这个:

/<<([^>]*+(?:>(?!>)[^>]*+)*+)>>/

正如@hobbs发布的正则表达式一样,只有在找到>后才能执行前瞻(与非贪婪量词相反,后者在每个位置都有效地进行前瞻)。但是这个使用了Friedl的“展开循环”技术,它应该比交替方法稍快一些。此外,所有量词都具有占有性,因此不需要保存可能使回溯成为可能的状态信息。

答案 2 :(得分:2)

在这种情况下使用否定字符类将起作用:

/<<([^>]*)>>//<<(.*)>>/具有相同的探测次数,因此与/<<(.*?)>>/

一样,回溯速度更快

我同意DVK;是正则表达式唯一的方法吗?

答案 3 :(得分:1)

在这种情况下,请查看专用解析器(例如Text::Balanced)的性能是否可以接受。它不是正则表达式,但如果没有关于“NB”poststcriptum的更多详细信息,那么在寻找仅使用正则表达式的解决方案时,您可能会有XY problem

如果您绝对必须使用正则表达式,请查看使用前瞻功能 - 它可以提高速度。

答案 4 :(得分:1)

假设你有一个简单的语法

my $p = Parse::RecDescent->new(<<'EOGrammar');
  program: assignment(s)

  assignment: id '=' '<<' angle_text '>>'
              { $return = [ $item{id}, $item{angle_text} ] }

  angle_text: <skip:undef> / ( [^>] | >(?!>) )* /x

  id: /\w+/
EOGrammar

的源文本
a = <<
Hello

World!

>>

b = <<


Goodbye
World!
>>

使用

处理结果时
for (@{ $p->program($text) }) {
  my($name,$what) = @$_;
  print "$name: [[[$what]]]\n";
}

你会看到

的输出
a: [[[
Hello

World!

]]]
b: [[[


Goodbye
World!
]]]