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

Hi, I was wondering if there could be an efficient way to get a
specified number of previous line texts from a very
large file while searching for a string text?

For example, I would like to get all the text within the
block marked between double dashes when the
searchfinds the string 'D : Error Occurred'.
Actually I only need the value of 'A' and 'B' only.

...
=================================
A : 123
B : 234
C : ABC
D : Error Occurred
=================================
...

  • Comment on Get specified number of previous line text when encountered a text match in a file

Replies are listed 'Best First'.
Re: Get specified number of previous line text when encountered a text match in a file
by saintmike (Vicar) on Aug 10, 2004 at 16:20 UTC
    Save them in a ring buffer:
    my $rex = qr/Fifth/; my @last_four; while(<DATA>) { push @last_four, $_; shift @last_four if @last_four > 4; print @last_four if $_ =~ $rex; } __DATA__ First Second Third Forth Fifth Sixth
Re: Get specified number of previous line text when encountered a text match in a file
by Zaxo (Archbishop) on Aug 10, 2004 at 16:21 UTC

    The simplest way is to redefine what is considered to be a line. That is done by setting $/ to the delimiter text.

    my @errors; { local $/ = "=================================\n"; open my $fh, '<', '/path/to/file.txt' or die $!; while (<$fh>) { chomp; push @errors, $_ if /D : Error Occurred/; } close $fh or die $!; }
    You can look up the A and B values with a regex on the raw text in @errors, or else break that into a hash with A..D as keys.

    After Compline,
    Zaxo

Re: Get specified number of previous line text when encountered a text match in a file
by davido (Cardinal) on Aug 10, 2004 at 16:15 UTC

    This assumes that you'll never see D: without first seeing A: and B: in the same record.

    my ( $adata, $bdata ); while ( <DATA> ) { chomp; m/^A:\s*(\S)$/ and $adata = $1; m/^B:\s*(\S)$/ and $bdata = $1; m/^D:/ and do { print "A: $adata, B: $bdata\n"; last; }; }

    Dave

Re: Get specified number of previous line text when encountered a text match in a file
by eserte (Deacon) on Aug 10, 2004 at 17:27 UTC
    As an alternative, you can take a look at Tie::File. This would let you handle the file as an array. Here's a completely untested piece of code, just to give you the idea:
    tie @l, "Tie::File", $file or die $!; for my $i (0 .. $#l) { if ($l[$i] =~ /Error Occurred/) { while($i > 0) { $i--; if ($l[$i] =~ /(A|B):\d+/) { print $l[$i]; exit; # or last } elsif ($l[$i] =~ /^====/) { exit; # or last } } } }
    However, Tie::File is not that efficient on large files.

    Another alternative could use a combination of a normal filehandle to go forward in the file, and File::ReadBackwards to go backward in the same file.

Re: Get specified number of previous line text when encountered a text match in a file
by Joost (Canon) on Aug 10, 2004 at 16:14 UTC
    my @last_lines; while (<>) { if ($_ eq "D : Error Occurred\n") { print @last_lines,$_; last; } push @last_lines,$_; shift @last_lines if @last_lines > 4; }
    Never mind. that's not what you want at all.

    If you really only need the first 2 lines after the last "====" line, then maybe this is a good start:

    my @last_lines; while (<>) { if ($_ eq "D : Error Occurred\n") { print @last_lines,$_; last; } if (/===/) { @last_lines =(); } elsif (@last_lines < 2) { push @last_lines,$_; } }
    updated: duplicate line removed.