Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Printing line before matching expression

by rm (Initiate)
on Sep 10, 2013 at 23:13 UTC ( #1053380=perlquestion: print w/replies, xml ) Need Help??

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

I want to print the nth line (typically 2nd) before the line which has matching expression. I have code below to print the nth line after matching expression. I'd like to see if we can keep the same structure. My first time here so pls be patient if I made elementary mistakes. thx!

open (INPUT, $ARGV[0]) or die "I couldn't get at input text"; open (OUTPUT, '>outfile.txt') or die "Can't write to outfile: $!"; while ($line = <INPUT>) { #chop ($line); if (( $line =~ /index_1/ ) and ($line =~ /6/)) { $i=0; while ($i<2) { $line = <INPUT>; # gets next line $i++; } print OUTPUT $line;} }

Replies are listed 'Best First'.
Re: Printing line before matching expression
by kcott (Bishop) on Sep 10, 2013 at 23:50 UTC

    G'day rm,

    Welcome to the monastery.

    You just need to keep a sufficient history of previous lines so that you go back to the previous nth line. Here's one way to do it:

    $ perl -Mstrict -Mwarnings -Mautodie -le ' my $file = "pm_1053380_data.txt"; my ($match, $before) = @ARGV; my @previous; open my $fh, "<", $file; while (<$fh>) { chomp; if ($_ eq $match) { print $previous[0]; } push @previous, $_; shift @previous if @previous > $before; } ' line4 2 line2

    The input file I used:

    $ cat pm_1053380_data.txt line1 line2 line3 line4 line5

    Notes:

    • You'll need to add some validation to check that you can actually look back the number of specified lines.
    • I see you had chop and then commented it out. You probably want chomp (as I used).

    -- Ken

Re: Printing line before matching expression
by johngg (Canon) on Sep 10, 2013 at 23:52 UTC

    You have to keep a buffer of previously read lines large enough to retain the nth previous line you are wanting to print. How to handle the situation where there aren't n lines before your found line is an exercise left to the reader.

    $ perl -Mstrict -Mwarnings -E ' open my $inFH, q{<}, \ <<EOD or die $!; line 1 line 2 line 3 line 4 line 5 line 6 line 7 line 8 line 9 EOD my $lineBeforeMatch = 5; my @prevLines; my $lookFor = qr{line 7}; while ( <$inFH> ) { push @prevLines, $_; shift @prevLines if $#prevLines > $lineBeforeMatch; print $prevLines[ 0 ] if m{$lookFor}; }' line 2 $

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Printing line before matching expression
by jaredor (Priest) on Sep 11, 2013 at 05:35 UTC

    For fun, here's a version that uses a linked list. The "defined" ensures that there is something to be printed. If you change $lookback to 4 and rerun, you'll see it in action.

    #!/usr/bin/env perl use strict; use warnings; my $lookback = 3; my $match = qr/\wiz/; my @lbuff = map { [] } 1..$lookback; $lbuff[$_-1][0] = $lbuff[($_) % $lookback] for 1..$lookback; my $curr = $lbuff[0]; while (<DATA>) { /$match/ and defined $$curr[1] and print "$$curr[1]"; ($$curr[1], $curr) = ($_, $$curr[0]); } __DATA__ foo bar baz biz buz goo car caz ciz cuz

    P.S. If anyone knows how to reduce the two-line circular linked list definition into one line of idiomatic perl I would love to see it. I'm too tired to think of something clever at the moment.

      It seems a bit of overhead to me if you use an array of the required buffer size to store the linked list and the data. What you save compared to other proposals is the pushing and shifting which involves a lot of copying data around. This can be done more easily by cycling through the buffer using the modulo operator on the line number of the data.

      use strict; use warnings; my $lookback = 3; my $match = qr/\wiz/; my @lbuff; while (<DATA>) { /$match/ and defined $lbuff[ $. % $lookback ] and print "$lbuff[ $ +. % $lookback ]"; $lbuff[ $. % $lookback ] = $_; } __DATA__ foo bar baz biz buz goo car caz ciz cuz

        compared to other proposals is the pushing and shifting which involves a lot of copying data around.

        Sure it doesn't :)

        This can be done more easily by cycling through the buffer using the modulo operator on the line number of the data.

        If all the data you want is at the end

        my @data = ( undef, undef, qw/ bingo nameo / ); print @data[ -1,-2,-3 ]; __END__ nameobingo

        A small suggestion would be adding one more slot to the buffer and using some perl idiomcyncracies (sic) to reduce the need to manipulate the buffer in the inner loop:

        #!/usr/bin/env perl use Modern::Perl; my $lb = 3; # lookback my $match = qr/\wiz/; my @buff; while ($buff[$.%($lb+1)] = ($_ = <DATA>)) { my $lbi = ($.+1)%($lb+1); # lookback index print $buff[$lbi] if defined $buff[$lbi] and /$match/; } __DATA__ foo bar baz biz buz goo car caz ciz cuz

        P.S. Thanks for optimizing my two line circular linked list declaration to zero lines! ;-)

        P.P.S. Too bad: Yours is, IMHO, the best answer to the OP, but is buried in this subthread.

        EDIT Just reviewed this code and saw that I forgot defined! This opens the door for error if the lookback line is empty or a zero. While I was at it, I changed the emphasis from the lookback line itself to the index that finds it; plus, no more relying on side effects in the conditional.

        For the three of y'all who upvoted the earlier incarnation, the original code is below. One line was deleted, one line added, one line changed.

Re: Printing line before matching expression
by uday_sagar (Scribe) on Sep 11, 2013 at 09:28 UTC

    Hope you would be interested in this!

    open (INPUT, $ARGV[0]) or die "I couldn't get at input text"; open (SAME_INPUT, $ARGV[0]) or die "I couldn't get at input text"; open (OUTPUT, '>outfile.txt') or die "Can't write to outfile: $!"; while ($line = <INPUT>) { if (( $line =~ /index_1/ ) and ($line =~ /6/)) { $index = $.-2; } } while ($line = <SAME_INPUT>) { if ($. == $index) { print OUTPUT $line; } }

      Although possibly not terribly efficient, I like the idea of using 2 file handlers, it makes the code quite simple. But I would change it to something like this:

      open my $INPUT, "<", $ARGV[0] or die "I couldn't get at input text"; open my $SAME_INPUT, "<", $ARGV[0] or die "I couldn't get at input tex +t"; my $offset = $ARGV[1]; my $line = <$INPUT> for (1..$offset); # discard the n first lines, can +'t do anything with them anyway while (<$INPUT>) { $line = <$SAME_INPUT>; print $line if /$regex/; }

      There is also the alternative of slurping the file into an array and walking through the array. Reading the minus $n line is then trivial.

        Yes, concept of offset is good! :-)

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (8)
As of 2021-03-06 18:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favorite kind of desktop background is:











    Results (118 votes). Check out past polls.

    Notices?