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

Monks, I open a file for reading . my program pattern matches for a word. It then prints that line and should also print the line above it. how do I do that?? my code looks like ......
use strict; use warnings; my $file; chomp ($file = <STDIN>); open (MYFILE,"<$file") or die $!; while (><MYFILE>) { if(/ALARM:/) { print "$.: $_"; } } close MYFILE;
  • Comment on when reading a file how to print a line one above the current line??
  • Download Code

Replies are listed 'Best First'.
(jeffa) Re: when reading a file how to print a line one above the current line??
by jeffa (Bishop) on Jun 04, 2002 at 04:35 UTC
    First, if you want to open a file in that manner, you need to should get the file name from @ARGV instead of STDIN:
    my $file = shift @ARGV;
    No need to chomp it either. Next, if you want to print the line above the current line, you will need to print the line previously read. You could do this by slurping the file into an array, or you could just store the previous line in another scalar:
    my $last; while (<MYFILE>) { if(/ALARM:/) { print $. - 1, ": $last"; print "$.: $_"; } $last = $_; }
    Notice subtracting one from $. to get the previous line number as well. Everything else looks acceptable, well everything except that typo in your while expression.

    jeffa

    Quiz on Friday ;)
      Actually, there are situations where one would like to read file names from STDIN -- e.g.:
      ls *.log | show-the-alarms
      in which case, it'll be handy to have the file name(s) included in the output (I haven't tested this):
      while ( $file = <>) { chomp $file; my $status = "open file"; open( MYFILE, $file ) or next; $status = 0; my $last = ""; while (<MYFILE>) { if(/ALARM:/) { print "$file:", $. - 1, ": $last"; print "$file:$.: $_"; $status++; } $last = $_; } close MYFILE; } continue { print "Done with $file : found $status error(s)\n"; }
      That might not be what the questioner had in mind, but it's a reasonable approach.
        You still shouldn't read the filenames from STDIN. What you mention can and should be done by the shell: show-the-alarms `ls *.log`. Adding this ill-advised "functionality" to the script will only make it unnecessarily difficult to parse data not coming from a file.

        Makeshifts last the longest.

      But why tell him to read a filename at all in the case? I find that if one is taking filenames on the commandline, it's much better to use the diamond operator and let perl decide whether I should be reading from a bunch of files or STDIN, which allows for more flexibility.

      And some technical critique - you didn't guard your print $last. This is what I have in mind:
      #!/usr/bin/perl use strict; use warnings; my $prevline; while (<>) { if(/ALARM:/) { print $. - 1, ": $prevline" if defined $prevline; print "$.: $_"; } $prevline = $_; }
      ____________
      Makeshifts last the longest.
        Good point, but, personally, I'd do it as
        my $prevline = ''; ... print $. - 1, ": $prevline";
        This way doesn't have the overhead of testing every time whether $prevline is defined. Or you could initialize it to "BOF\n" for a more explicit indication of what's going on.
Re: when reading a file how to print a line one above the current line??
by Abigail-II (Bishop) on Jun 04, 2002 at 11:35 UTC
    You might want to look into the module Tie::File which comes with perl 5.8. Alternatively, use something like

    open my $fh => $file or die "Failed to open $file: $!\n"; my $last = ""; while (<$fh>) { print "$last$_" if /ALARM:/; $last = $_; } close $fh;

    Abigail