2009-09-25 27 views
5

Tôi có một danh sách ngày càng tăng các biểu thức chính quy mà tôi đang sử dụng để phân tích cú pháp thông qua các tệp nhật ký tìm kiếm các lỗi "thú vị" và các câu lệnh gỡ rối. Tôi hiện đang chia chúng thành 5 thùng, với hầu hết trong số chúng rơi vào 3 thùng lớn. Tôi có hơn 140 mẫu cho đến nay, và danh sách đang tiếp tục phát triển.Làm thế nào tôi có thể so khớp hiệu quả nhiều mẫu regex khác nhau trong Perl?

Hầu hết các cụm từ thông dụng đều đơn giản, nhưng chúng cũng khá độc đáo, vì vậy cơ hội của tôi để bắt nhiều kết quả phù hợp với một mẫu đơn rất ít. Bởi vì bản chất của những gì tôi phù hợp, các mẫu có xu hướng tối nghĩa và ít khi so khớp, vì vậy tôi đang làm một TON công việc trên mỗi dòng đầu vào với kết quả cuối cùng là nó không khớp với bất cứ điều gì, hoặc phù hợp một trong những cái chung chung ở cuối cùng.

Và vì số lượng đầu vào (hàng trăm megabyte tệp nhật ký), đôi khi tôi chờ một hoặc hai phút để tập lệnh kết thúc. Do đó mong muốn của tôi cho một giải pháp hiệu quả hơn. Tuy nhiên, tôi không quan tâm đến việc hi sinh sự rõ ràng về tốc độ.

Tôi hiện có các biểu thức thông thường thiết lập như thế này:

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

Có cách nào tốt hơn về cơ cấu này vì vậy nó hiệu quả hơn, nhưng vẫn duy trì? Cảm ơn!

Trả lời

5

Bạn có thể muốn xem Regexp::Assemble. Nó nhằm xử lý chính xác loại vấn đề này.

Boosted mã từ tóm tắt của mô-đun:

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]) 

Bạn thậm chí có thể húp bộ sưu tập regex của bạn ra khỏi một file riêng biệt.

+2

Chắc chắn là con đường để đi. Tôi có một ứng dụng hiện đang trong quá trình sản xuất sử dụng Regexp :: Assemble để so sánh chuỗi văn bản đến với danh sách 1.334 cụm từ để xem (nếu có) trong số đó nằm trong mỗi chuỗi. Mã đơn giản như địa ngục và chạy tốt đẹp và nhanh chóng. –

5

Bạn có thể kết hợp regexes của bạn với các nhà điều hành luân phiên |, như trong: /pattern1|pattern2|pattern3/

Rõ ràng, nó sẽ không thể rất duy trì nếu bạn đặt tất cả trong số họ trong một đường duy nhất, nhưng bạn đã có tùy chọn để giảm thiểu điều đó.

  • Bạn có thể sử dụng công cụ sửa đổi regex /x để không gian chúng độc đáo, mỗi dòng một dòng. Một lời cảnh cáo nếu bạn chọn hướng này: bạn sẽ phải chỉ định rõ ràng các ký tự khoảng trắng bạn mong muốn, nếu không chúng sẽ bị bỏ qua vì số /x.
  • Bạn có thể tạo biểu thức chính quy của mình tại thời gian chạy, bằng cách kết hợp các nguồn riêng lẻ. Một cái gì đó như thế này (chưa được kiểm tra):

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

+1 Điều này thực sự có thể hiệu quả hơn là thực hiện chúng một cách riêng biệt. Công cụ regexp có thể thông minh và rút ra các tiền tố phổ biến có thể tăng tốc độ (điều này đúng với perl 5.10 ít nhất, và có thể trước đó). –

+4

/o đã lỗi thời/không dùng nữa. Sử dụng qr //. Và tôi nghĩ trong những lúng túng hiện đại, miễn là regex $ không thay đổi, sau đó nó sẽ không được biên dịch lại. Nhưng không báo cho tôi :-) – runrig

+0

@runrig Tôi nhớ lại đọc một cái gì đó về/o không được chấp nhận, nhưng không thể tìm thấy bất cứ điều gì về nó trong tài liệu. Theo như tôi có thể nhớ, nó là cần thiết trong 5.8.8 (hiện đại như thế nào? Không báo cho tôi một trong hai). qr // tránh vấn đề, nhưng cách xa bước logic tiếp theo trong lý do của tôi: đọc danh sách regex từ các nguồn khác (ví dụ: tệp riêng) –

1

Một giải pháp khả thi là để cho máy nhà nước regex làm việc kiểm tra lựa chọn thay thế cho bạn. Bạn sẽ phải điểm chuẩn để xem kết quả có hiệu quả hơn hay không, nhưng chắc chắn nó sẽ dễ bảo trì hơn.

Trước tiên, bạn sẽ duy trì tệp chứa một mẫu sở thích trên mỗi dòng.

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

Sau đó, bạn muốn đọc trong tập tin đó vào đầu chạy của bạn, và xây dựng một biểu thức chính quy lớn sử dụng các nhà điều hành "thay thế", "|"

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

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

Có lẽ cái gì đó như:

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; 
    } 
} 

Bạn có thể thử tham gia tất cả các mẫu của mình bằng "|" để tạo một regex. Điều đó có thể hoặc không thể nhanh hơn.

4

Bạn có thể muốn thoát khỏi sự lớn nếu tuyên bố:

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

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

mặc dù tôi không thể hứa hẹn này sẽ cải thiện bất cứ điều gì w/o benchmarking với dữ liệu thực tế.

Nó có thể hữu ích nếu bạn có thể neo mẫu của bạn ngay từ đầu để chúng có thể thất bại nhanh hơn.

+0

Sinan - +1 nhưng hãy nhớ rằng anh ấy phân tích tệp nhật ký, vì vậy, các chuỗi sẽ không bị đe dọa, rất có thể. Nhưng hy vọng nhật ký của anh ta có tiền tố thống nhất để anh ta có thể neo dấu thời gian hoặc bất kỳ tiền tố nào giống như vậy. – DVK

+0

Thật vậy, tôi thường rất tôn giáo về việc neo các chuỗi của mình, nhưng như DVK nói, các chuỗi có thể và sẽ ở khắp mọi nơi (đặc biệt là khi tôi phân tích cú pháp các loại tệp nhật ký khác nhau). Dấu thời gian RE là lần đầu tiên tôi làm và tất nhiên là được neo (khi tôi tìm ra dấu thời gian RE để sử dụng). –

+0

Đường ghi thường có một số trường được xác định. Tôi đã suy nghĩ dọc theo các dòng loại bỏ những lĩnh vực đầu tiên trước khi nhìn vào trận đấu có thể. –

1

Cụm từ thông dụng mẫu của bạn trông giống như chúng chủ yếu dựa trên các từ và cụm từ thông thường. Nếu đúng như vậy, bạn có thể tăng tốc độ đáng kể bằng cách lọc trước các dòng đầu vào bằng cách sử dụng index, nhanh hơn nhiều so với cụm từ thông dụng. Theo một chiến lược như vậy, mọi cụm từ thông dụng sẽ có từ hoặc cụm từ không phải regex tương ứng để sử dụng trong giai đoạn tiền lọc. Tốt hơn là sẽ bỏ qua toàn bộ bài kiểm tra biểu thức chính quy, bất cứ khi nào có thể: hai trong số các bài kiểm tra ví dụ của bạn không yêu cầu biểu thức chính quy và có thể được thực hiện hoàn toàn với index.

Dưới đây là một minh họa về ý tưởng cơ bản:

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($_)) } }, 
}); 

Output (kết quả với dữ liệu của bạn có thể rất khác nhau):

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

Bạn nên sử dụng '. +?' trong regex. –

2

này được xử lý một cách dễ dàng với 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){ 
     # ... 
    } 
    } 
} 

Bạn có thể sử dụng Smart-Match operator ~~ mới.

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

Hoặc bạn có thể sử dụng given/when. Thực hiện giống như sử dụng toán tử Smart-Match.

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

Từ câu trả lời perlfaq6 's để How do I efficiently match many regular expressions at once?


Làm thế nào để có hiệu quả phù hợp với nhiều biểu thức thông thường cùng một lúc?

(đóng góp bởi brian d Foy)

Tránh hỏi Perl để biên dịch một biểu hiện thường xuyên mỗi khi bạn muốn để phù hợp với nó. Trong ví dụ này, perl phải biên dịch lại biểu thức chính quy cho mỗi lần lặp của vòng lặp foreach vì nó không có cách nào để biết mẫu $ sẽ là gì.

@patterns = qw(foo bar baz); 

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

Toán tử qr // xuất hiện trong perl 5,005. Nó biên dịch một biểu thức chính quy, nhưng không áp dụng nó. Khi bạn sử dụng phiên bản được biên dịch trước của regex, perl sẽ ít hoạt động hơn. Trong ví dụ này, tôi đã chèn một bản đồ để biến từng mẫu thành dạng được biên dịch trước của nó. Phần còn lại của kịch bản là như nhau, nhưng nhanh hơn.

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

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

Trong một số trường hợp, bạn có thể tạo nhiều mẫu thành một biểu thức chính quy duy nhất. Hãy coi chừng các tình huống yêu cầu backtracking.

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

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

Để biết thêm chi tiết về hiệu quả biểu thức chính quy, hãy xem Làm chủ biểu thức chính quy của Jeffrey Freidl. Ông giải thích cách thức hoạt động của các công thức biểu thức thông thường và tại sao một số mẫu lại không hiệu quả đáng ngạc nhiên. Khi bạn hiểu cách perl áp dụng cụm từ thông dụng, bạn có thể điều chỉnh chúng cho từng trường hợp.

Các vấn đề liên quan