Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Print 5 lines before and after pattern match from a list

by jayu_rao (Sexton)
on Mar 14, 2015 at 15:55 UTC ( [id://1120058]=perlquestion: print w/replies, xml ) Need Help??

jayu_rao has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I am wanting to print 5 lines before and after a pattern match from a list. I have written the below code but its repeating the loop improperly and writing the lines more than once.

I am sure I am doing something silly but not able to figure it out. Can you anyone of you please help?

#!/usr/bin/env perl use strict; use diagnostics; open my $read_log, '<', '/home/jayu_rao/test_output/app.log' or die $! +; open my $write_tmp_app_log, '>', '/home/jayu_rao/test_output/write_tmp +_app.log' or die $!; my @patterns = ( qr/.*Error.*/, qr/.*error.*/, qr/.*ERROR.*/, qr/.*FATAL.*/, qr/.*Critical.*/, qr/.*exception.*/, ); my @lines=<$read_log>; my $i; for (@patterns){ for $i (0..$#lines) { next unless $lines[$i] =~ $_; my $a = $i - 5 < 0 ? 0 : $i - 5;; my $b = $i + 5 > $#lines ? $#lines : $i + 5; for $i($a..$b){ print $write_tmp_app_log $lines[$i] }; } } close $read_log; close $write_tmp_app_log;

Replies are listed 'Best First'.
Re: Print 5 lines before and after pattern match from a list
by hdb (Monsignor) on Mar 14, 2015 at 16:33 UTC

    You have two nested loops using $i as the looping variable. Just change the inner one to something else:

    for my $j($a..$b){ print $write_tmp_app_log $lines[$j] };

    UPDATE: added my to $j.

    UPDATE: avoid the inner loop altogether:

    print $write_tmp_app_log @lines[$a..$b];

    UPDATE: I guess if you just had added a my to the $i in the inner loop like for my $i($a..$b){ it would work as well but for my personal feeling that would be to dangerous.

      The OP and your second update are reminders of why and how it's preferable to avoid C style loops in Perl. Perl's looping tools make C loops easy enough to avoid, and using C loops is an easy way to produce hard to notice but annoying bugs.

Re: Print 5 lines before and after pattern match from a list
by bitingduck (Chaplain) on Mar 14, 2015 at 16:33 UTC

    Edit: I managed to create a dummy file that printed according to what jayu_rao wanted, despite the double use of the counter noticed by hdb!

    ----original post below----

    Are your caught lines not just too close together? Try adding the marker and counter that I put in below and look at the results. Also, if you make the match case insensitive you can compress the first three patterns into one. I tested it with the dummy file in the spoiler:

    #!/usr/bin/env perl use strict; use diagnostics; open my $read_log, '<', '/home/jayu_rao/test_output/app.log' or die $! +; open my $write_tmp_app_log, '>', '/home/jayu_rao/test_output/write_tmp +_app.log' or die $!; my @patterns = ( qr/.*Error.*/, qr/.*error.*/, qr/.*ERROR.*/, qr/.*FATAL.*/, qr/.*Critical.*/, qr/.*exception.*/, ); my @lines=<$read_log>; my $i; my $counter=0; for (@patterns){ for $i (0..$#lines) { next unless $lines[$i] =~ $_; my $a = $i - 5 < 0 ? 0 : $i - 5;; my $b = $i + 5 > $#lines ? $#lines : $i + 5; for $i($a..$b){ print $write_tmp_app_log $lines[$i] }; $counter++; print $write_tmp_app_log "----------marker$counter------------"; } } close $read_log; close $write_tmp_app_log;
Re: Print 5 lines before and after pattern match from a list
by Athanasius (Archbishop) on Mar 15, 2015 at 03:38 UTC

    Hello jayu_rao,

    A side note: In the regular expression qr/.*Error.*/, .* means “match zero or more characters1”, so it has the same meaning as the simpler regex qr/Error/. Besides being shorter, simpler, and (IMO) clearer, the latter is also about 6% faster:

    1That is, zero or more of any character other than newline. To match newlines, an /s modifier is needed.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Print 5 lines before and after pattern match from a list
by oiskuu (Hermit) on Mar 15, 2015 at 14:48 UTC

    Here's a filter version (read from stdin, write to stdout) of this, um, filter. I've also added the before/after context parameters as program options. (Consistent with the grep utility.)

    Note the use of a small queue (@lines) to buffer the before-context. And notice the versatility of the splice function.

    #!/usr/bin/env perl use strict; use warnings; use Getopt::Std; my @patterns = ( qr/error/i, qr/FATAL/, qr/Critical/, qr/exception/, ); my $re = join '|', @patterns; my %opt = ( B => 5, A => 5 ); getopts('B:A:', \%opt); my $window = $opt{B} + $opt{A} + 1; my $emit = 0; my @lines; while (<>) { $emit = $window if /$re/; splice(@lines, 0, 0, $_); --$emit < 0 ? splice(@lines, $opt{B}) : print splice(@lines, -1); }
    Edit: above code is deficient both at the start and end of the stream. See if you can fix it...

      I don't think the attribute "deficient" applies so much to your code as to OP's imperfect spec. Clearly, if an element of @patterns occurs in first line of the log being read, lines ( -n .. 0 ) simply do NOT exist (for any value other than 0 of the prior lines desired). We can speculate, but OP alone knows (or 'should know') the complete spec.

      Similarly, if any element of @patterns occurs in the last line, the lines ( (last+1)..(last+5) ) are going to be a bit difficult to include in the output.

      BUT, far more important than the observations above, your code (Re: Print 5 lines before and after pattern match from a list) and that above same title, different node, by bitingduck are excellent examples of what Monks can do to help a Seeker, as is hdb's initial response re the looping mistake and Athanasius' note on regular expressions.

      ++ to all.

        The OPs code already implements a solution by cutting of at the beginning and the end of the array, so we can safely imply his desired spec!

Re: Print 5 lines before and after pattern match from a list
by LanX (Saint) on Mar 15, 2015 at 16:46 UTC
Re: Print 5 lines before and after pattern match from a list
by trippledubs (Deacon) on Mar 16, 2015 at 00:50 UTC
    not Perl, but also at 'nix shell -- grep/egrep paramaters -A (after) and -B (before).
    alpha bravo charlie delta echo foxtrot
    # egrep -B 1 'charlie|foxtrot' t bravo charlie -- echo foxtrot

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1120058]
Approved by hdb
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2024-04-25 18:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found