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;
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. | [reply] [d/l] [select] |
|
| [reply] |
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;
| [reply] [d/l] [select] |
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,
| [reply] [d/l] [select] |
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... | [reply] [d/l] [select] |
|
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.
| [reply] [d/l] [select] |
|
| [reply] |
Re: Print 5 lines before and after pattern match from a list
by LanX (Saint) on Mar 15, 2015 at 16:46 UTC
|
| [reply] |
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
| [reply] [d/l] [select] |
|
|