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

Hello monks quick question from a beginning perlist: I have a program that goes through an input file line for line and modifies this input using regexes. That part is working great EXCEPT I need to use a different regex on the LAST LINE only. After hours trying different setups (i.e. banging head on desk), I have decided to seek assistance. Here's what I have thus far:
open(TXTIN,"ARGV[0]") || die "Cannot open the data file"; open(TXTOUT,">ARGV[1]") || die "Cannot open the formatted file"; #$itercnt=0 while(<TXTIN>) {#counting number of lines in input file $itercnt++ } for ($match=0;$match<$itercnt;$match++){ while(TXTIN){ chomp $_; $_ =~ s/yada/yadda/; print TXTOUT $_; }} #for (;$match=$itercnt;$match++){ # while(TXTIN){ # chomp $_; #$_ =~ s/different/regex here/; #print TXTOUT $_; #}} # getting stuck in infinite loop in here somewhere # so I haven't been able to see if it is working close(TXTIN); close(TXTOUT);
So the input file can have anywhere from 1 to 100 lines, I'll never know before hand what the number will be. I've already tried 'eof' and it doesn't work because by the time it is 'true' the last line has already been modified/written. So, (A) am I going about this the totally wrong way? (B) if so, is there a better way to do this? Please keep in mind I am a novice, and would like to at least understand what is going on. And if you see any glaring defects feel free to point them out. TIA- Joe

Replies are listed 'Best First'.
Re: While.For loop noob query
by Roy Johnson (Monsignor) on Oct 06, 2005 at 14:34 UTC
    For a small file (1 to 100 lines would qualify), you can just read the whole thing into an array at the start. Then you know how many lines you have.
    my @lines = <TXTIN>; for (@lines[0..($#lines - 1)]) { s/yada/yadda/; } $lines[-1] =~ s/different/substitution/;

    Caution: Contents may have been coded under pressure.
      Hi Roy, Quick follow up if you are still in here..What is this doing?
      for (@lines[0..($#lines - 1)])
      it's kind of hard for me to see what's going on..Would you mind translating somewhat so I can adjust to my particular needs, if necessary. Thanks again
        It's going through all the elements of @lines except the last one. $#lines is the index of the last element of @lines. So 0..($#lines - 1) is therefore the range from zero to index of the next-to-last element. So @lines[0..($#lines - 1)] is a slice of all but the last element.

        Caution: Contents may have been coded under pressure.

        $#lines is the index of the last elements of the array @lines. Let's say you have 10 lines. Their indexes would be 0 through 9, so $#lines would be 9.

        @lines[ LIST ] is an array slice. It returns a list formed from the elements indexed by LIST. In our earlier example, @lines[0..($#lines - 1)] returns elements 0 through 8.

        The for here starts a foreach loop over each element of the returned list. In other words, it loops over all up the last element of @lines. Since no variable name is specified, $_ will hold the value for the current iteration.

        I think $#array and array slices are documented in perldata. Loops are documented in perlsyn.

Re: While.For loop noob query
by davidrw (Prior) on Oct 06, 2005 at 14:28 UTC
    Using eof is the right approach... What was your eof attempt? Looking at your code above, you can't count the lines like that without reseting the file before reading the whole thing again.. I believe that this should work (untested):
    open(TXTIN,"ARGV[0]") || die "Cannot open the data file"; open(TXTOUT,">ARGV[1]") || die "Cannot open the formatted file"; while(<TXTIN>){ # gets a line from input file if( eof TXTIN ){ # eof returns true if the _next_ re +ad would fail, meaning s/different/regex here/; # the one we just read was the l +ast line }else{ s/yada/yadda/; } print TXTOUT $_; } close(TXTIN); close(TXTOUT);
    Also, i don't think you want to chomp the input and then not write a newline to the output file (so i just omitted the chomp from my snippet)...

    Note that (and this one i tested) you can also use the commandline here:
    perl -pe 'if( !eof ){ s/^1/AAAA/; } else{ s/^1/BBBB/; }' /etc/hosts > +/tmp/newfile
    And if you want to do an in-place edit (see perlrun) you can just add the -i command-line parameter.

    one last note: if you're not doing use strict; and use warnings;, they will be invaluable to you to add.. (maybe you just omitted them from your code snippet -- just worth double-checking)

    ok, one last note-- it doesn't apply to this specific task, but the $. variable might be of interest to you -- see perlvar (e.g. if you wanted the first line to have a different regex)
Re: While.For loop noob query
by Perl Mouse (Chaplain) on Oct 06, 2005 at 14:21 UTC
    I would do something like:
    if (defined (my $previous_line = <>)) { while (my $current_line = <>) { do_regular_thing_with $previous_line; $previous_line = $current_line; } do_special_thing_with $previous_line; }
    What you are doing is keeping a one-line buffer. Read in the next line, if there is one, do your regular thing with the line in the buffer. If there's no next line, the thing in your buffer is the last line, so do your special thing then.

    No need to count lines.

    Perl --((8:>*
Re: While.For loop noob query
by blazar (Canon) on Oct 06, 2005 at 15:00 UTC
    do_something special if eof($fh);
    In any case read about eof. Of course the suggestion above brings you to this issue (which IMHO is worth reading -- since it was me to bring it up in the first place in p6l ;-).
Re: While.For loop noob query
by McDarren (Abbot) on Oct 06, 2005 at 15:00 UTC
    I see you already have a few responses, but here's mine anyway:
    #!/usr/bin/perl -w use strict; open (IN, "<infile.txt") or die "$!\n"; open (OUT, ">outfile.txt") or die "$!\n"; # Read the entire file into an array my @lines = <IN>; close IN; # An array evaluated in scalar context returns the number of elements my $numlines = @lines; # Iterate through the array, stopping at the second last element # Array indexes start from 0, so we stop at $numlines -2 my $cnt; for ($cnt=0; $cnt<=$numlines-2; $cnt++) { # Do stuff... $lines[$cnt] =~ # blah...blah... } # Index -1 always refers to the last element # So process the last line separately here... $lines[-1] =~ # blah...blah... # Now, print the lot print OUT @lines; close OUT;

    --Darren
Re: While.For loop noob query
by ikegami (Patriarch) on Oct 06, 2005 at 14:34 UTC
    Or if you don't mind reading the file from the end:
    use File::ReadBackwards () my $fh_in = File::ReadBackwards->new($ARGV[0]) or die("Unable to open the data file: $!\n"); open(my $fh_out, '>', $ARGV[1]) or die("Unable to create output file: $!\n"); local $_; my $last = 1; while (defined($_ = $fh_in->readline())) { if ($last) { s/different/regex here/; } else { s/yada/yadda/; } print $fh_out $_; $last = 0; }
    File::ReadBackwards

    Update: Nevermind, the output file will end up being backwards. If only there was a File::WriteBackwards module ;)