如何在Perl中有效地匹配许多不同的正则表达式模式?

时间:2009-09-25 15:43:09

标签: regex perl

我有越来越多的正则表达式,我用它来解析搜索“有趣”错误和调试语句的日志文件。我现在将它们分成5个桶,其中大部分落入3个大桶中。到目前为止,我有140多种模式,而且这个模式还在继续增长。

大多数正则表达式都很简单,但它们也相当独特,所以我用单一模式捕捉多个匹配的机会很少。由于我匹配的性质,模式往往是模糊的,因此很少匹配,所以我在每个输入行上做一个TON工作,最终结果是它无法匹配任何东西或匹配最后的通用之一。

由于输入的数量(数百兆字节的日志文件),我有时会等待一两分钟才能完成脚本。因此,我希望有一个更有效的解决方案。不过,我对牺牲速度的清晰度并不感兴趣。

我目前的正则表达式设置如下:

 if (($line =~ m{Failed in routing out}) ||
  ($line =~ m{Agent .+ failed}) ||
  ($line =~ m{Record Not Exist in DB}) ||
         ...

有没有更好的方法来构建它,以便它更有效,但仍然可维护?谢谢!

8 个答案:

答案 0 :(得分:5)

您可以将正则表达式与交替运算符|结合使用,如:/pattern1|pattern2|pattern3/

显然,如果你将所有这些都放在一行中就不会很难维护,但是你已经 有办法减轻这种情况。

  • 您可以使用/x正则表达式修改器将它们很好地隔开,每行一个。如果你选择这个方向,请注意:你必须明确指定你期望的空格字符,否则因/x而被忽略。
  • 您可以通过组合各个来源在运行时生成正则表达式。像这样(未经测试):

    my $regex = join '|', @sources;
    while (<>) {
        next unless /$regex/o;
        say;
    }
    

答案 1 :(得分:5)

您可能需要查看Regexp::Assemble。它旨在解决这类问题。

来自模块概要的提升代码:

use Regexp::Assemble;

my $ra = Regexp::Assemble->new;
$ra->add( 'ab+c' );
$ra->add( 'ab+-' );
$ra->add( 'a\w\d+' );
$ra->add( 'a\d+' );
print $ra->re; # prints a(?:\w?\d+|b+[-c])

您甚至可以从单独的文件中篡改正则表达式集合。

答案 2 :(得分:4)

你可能想要摆脱大的if语句:

my @interesting = (
  qr/Failed in routing out/,
  qr/Agent .+ failed/,
  qr/Record Not Exist in DB/,
);

return unless $line =~ $_ for @interesting;

虽然我不能保证这会改善任何不用真实数据进行基准测试的东西。

如果你可以在一开始就锚定你的模式,那么它们可能会有所帮助。

答案 3 :(得分:3)

perlfaq6回答How do I efficiently match many regular expressions at once?


如何有效地匹配多个正则表达式?

(由brian d foy提供)

每次要匹配时,请避免让Perl编译正则表达式。在这个例子中,perl必须为foreach循环的每次迭代重新编译正则表达式,因为它无法知道$ pattern将是什么。

@patterns = qw( foo bar baz );

LINE: while( <DATA> )
    {
    foreach $pattern ( @patterns )
        {
        if( /\b$pattern\b/i )
            {
            print;
            next LINE;
            }
        }
    }

qr //运算符出现在perl 5.005中。它编译正则表达式,但不应用它。当您使用正则表达式的预编译版本时,perl会减少工作量。在这个例子中,我插入了一个地图,将每个模式转换为预编译形式。脚本的其余部分是相同的,但速度更快。

@patterns = map { qr/\b$_\b/i } qw( foo bar baz );

LINE: while( <> )
    {
    foreach $pattern ( @patterns )
        {
        if( /$pattern/ )
            {
            print;
            next LINE;
            }
        }
    }

在某些情况下,您可以将多个模式组合成一个正则表达式。请注意需要回溯的情况。

$regex = join '|', qw( foo bar baz );

LINE: while( <> )
    {
    print if /\b(?:$regex)\b/i;
    }

有关正则表达式效率的更多详细信息,请参阅Jeffrey Freidl掌握正则表达式。他解释了正则表达式引擎如何工作以及为什么某些模式效率低得惊人。一旦你理解了perl如何应用正则表达式,你就可以根据具体情况调整它们。

答案 4 :(得分:2)

您的示例正则表达式看起来主要基于普通单词和短语。如果是这种情况,您可以通过使用index预过滤输入行来大大加快速度,这比正则表达式快得多。在这样的策略下,每个正则表达式将具有用于预过滤阶段的相应的非正则表达词或短语。更好的方法是尽可能完全跳过正则表达式测试:你的两个示例测试不需要正则表达式,只能用index完成。

以下是基本概念的说明:

use strict;
use warnings;

my @checks = (
    ['Failed',    qr/Failed in routing out/  ],
    ['failed',    qr/Agent .+ failed/        ],
    ['Not Exist', qr/Record Not Exist in DB/ ],
);
my @filter_strings = map { $_->[0] } @checks;
my @regexes        = map { $_->[1] } @checks;

sub regex {
    my $line = shift;
    for my $reg (@regexes){
        return 1 if $line =~ /$reg/;
    }
    return;
}

sub pre {
    my $line = shift;
    for my $fs (@filter_strings){
        return 1 if index($line, $fs) > -1;
    }
    return;
}

my @data = (
    qw(foo bar baz biz buz fubb),
    'Failed in routing out.....',
    'Agent FOO failed miserably',
    'McFly!!! Record Not Exist in DB',
);

use Benchmark qw(cmpthese);
cmpthese ( -1, {
    regex => sub { for (@data){ return $_ if(            regex($_)) } },
    pre   => sub { for (@data){ return $_ if(pre($_) and regex($_)) } },
} );

输出(您的数据结果可能会有很大不同):

             Rate     regex prefilter
regex     36815/s        --      -54%
prefilter 79331/s      115%        --

答案 5 :(得分:2)

使用Perl 5.10

可以轻松处理
use strict;
use warnings;
use 5.10.1;

my @matches = (
  qr'Failed in routing out',
  qr'Agent .+ failed',
  qr'Record Not Exist in DB'
);

# ...

sub parse{
  my($filename) = @_;

  open my $file, '<', $filename;

  while( my $line = <$file> ){
    chomp $line;

    # you could use given/when
    given( $line ){
      when( @matches ){
        #...
      }
    }

    # or smartmatch
    if( $line ~~ @matches ){
      # ...
    }
  }
}

您可以使用新的Smart-Match operator ~~

if( $line ~~ @matches ){ ... }

或者您可以使用given/when。其功能与使用Smart-Match运算符相同。

given( $line ){
  when( @matches ){
    #...
  }
}

答案 6 :(得分:1)

一种可能的解决方案是让正则表达式状态机为您检查备选方案。你必须进行基准测试,看看结果是否明显更有效,但它肯定会更易于维护。

首先,您需要维护一个包含每行感兴趣模式的文件。

Failed in routing out
Agent .+ failed
Record Not Exist in DB

然后你在运行开始时读取该文件,并使用“alternative”运算符“|”构建一个大的正则表达式

open(PATTERNS,"<foo.txt") or die $!;
@patterns = <PATTERNS>;
close PATTERNS or die $!;
chomp @patterns;
$matcher = join('|', @patterns);

while (<MYLOG>) {
    print if $_ =~ $matcher;
}

答案 7 :(得分:1)

可能是这样的:

my @interesting = (
  qr/Failed in routing out/,
  qr/Agent .+ failed/,
  qr/Record Not Exist in DB/,
);

...


for my $re (@interesting) {
  if ($line =~ /$re/) {
    print $line;
    last;
  }
}

您可以尝试使用“|”加入所有模式做一个正则表达式。这可能会或可能不会更快。