http://www.perlmonks.org?node_id=24446

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

Dear Monks: If I want to read the last line of a file straightaway is there some way to do it other than to read the file line by line & then keep on checking for eof Thanks-vnpandey

Replies are listed 'Best First'.
Re: end of file!
by mrt (Acolyte) on Jul 26, 2000 at 18:54 UTC
    Dear Mr. pandey..If you first find the size of the file & then read the last 100-200 bytes or so..then try to find if there is a newline character in these bytes..if yes go ahead to split this with newline character and then take the last part(in case the end of line is new line character else any-way you can fix the input record seperator as reqd.) In case you do not find it then read last 300 bytes or so and continue till you find it...In this way you'll need only the minimum memory reqd.finding the size of the file in bytes is trivial any-way. As youknow youmay use read(FILEHANDLE,VAR,LENGTH,OFFSET) for ex.. let us suppose you have a file of 5000bytes.. now to get the last 100 characters from it
    #!usr/local/bin/perl open(FILE ,"name_of_the_file_to_be_read"); seek(FILE, 4900,0); read(FILE,$ab,100); #note the last 100 bytes get stored in $ab close(FILE);
    Now you can very well check for the newline character in $ab and then No need of any Module etc.... isn't it?? -MRT
      Wow!! It really works... Thanks..This seems to be one of the the smartest way one could think of!!! Thanks a lot......... :-Pandey
Re: end of file!
by fundflow (Chaplain) on Jul 26, 2000 at 15:41 UTC
    If the file is small, then the above answer by le is acceptable.

    If you expect the file to be large (such as log file) and you know the approximate format of the file (e.g. the lines tend to have max width of 80 characters), then you can use seek(-81,2) and then use the above solution to get the last 1.5 lines.

    You might want to check if it contains a newline, as otherwise you probably need to read more.
    Here is a code snippet:
    open (F,"the file"); seek(F,-81,2); while(<F>) { $l=$_} warn if $.<1; # Make sure we read more than one line
      If you know what the longest the last line in the file is, this can work nicely. You do the seek as above and then put the rest of the file in an array, here is an example:
      my @data; # Store the last part of the file my $length = -10; # enter the longest length of the last line (and add + a few bytes to it), then negate it open (F,"test") || die "Can't open file"; # open the file or die seek (F,$length,2); # Go back $length @data = <F>; # Get the rest of the file in @data print $data[$#data]; # Print the last line
      Hope this helps.
Re: end of file!
by ColtsFoot (Chaplain) on Jul 26, 2000 at 15:34 UTC
    If you want a quick and dirty hack and your OS supports
    the "tail" command, try the following
    print system('tail -1 the_file_you_want_last_line_from');
    There may be other more elegant perl ways but this works!!
      Thanks but I was trying to get solution using perl code itself i.e. something which reads the file in reverse order..(I know it can be done throgh tail commond too!!)
Re: end of file!
by jjhorner (Hermit) on Jul 26, 2000 at 19:38 UTC

    With a little help from the Perl Cookbook:

    #!/usr/bin/perl -w use strict; my $filename = shift; my $line; open (FILE, "$filename") || die "Couldn't open file: $!\n"; do {$line = <FILE>} until eof; print "$line";
    J. J. Horner
    Linux, Perl, Apache, Stronghold, Unix
    jhorner@knoxlug.org http://www.knoxlug.org/
    
      Hi!! Your reply is workable but involves a long process.. as the seeker of wisdom has told in further comments that the file is too big, this method may take a lot of time as it has to read eachline again and again lines and check for eof.. which the seeker already does not wanted to do, as is very clear from his question. Also for programmes repeating this code again and again it may be really be unwanted way to read the last line.. yes perl cook book gives it just an example and does not recommends this as very good way to do the same job, infact it expects us to read the codes and use them in best possible ways depending upon situation ......
Re: end of file!
by le (Friar) on Jul 26, 2000 at 15:29 UTC
    You could slurp the file into an array and then get the last array offset.
    @array = <FILE>; $last_line = $array[-1];
      Thanks...Yes it is one of the ways but is there some better way as the file is pretty huge & there is memory crunch so reading the whole file seems to be really wasting a lot of memory.. is there something which reads the file in reverse order. Thanks-vnpandey
        Indeed, there is a module called File::ReadBackwards available through CPAN. From the description in the man file:
        This module reads a file backwards line by line. 
        It is simple to use, memory efficient and fast. 
        It supports both an object and a tied handle interface. 
        
        It is intended for processing log and other similar text 
        files which typically have their newest entries appended 
        to them. By default files are assumed to be plain text and
        have a line ending appropriate to the OS. But you can set
        the input record separator string on a per file basis. 
        
        Autark.
Re: end of file!
by japhy (Canon) on Jul 27, 2000 at 23:23 UTC
    The problem with filesystems is that files are treated as streams of data, instead of linked lists of lines. If files were linked lists, then insertion and deletion would be a snap, as well as traversing the list to a certain line.

    As it is, the fastest way is to read X bytes from the end of the file, and check for the right-most \n character (that is NOT at the end of the string).
    sub last_line { my ($fref, $r_len) = @_; my ($last, $chomped) = ("", 0); $r_len = -$r_len if $r_len > 0; $r_len ||= -80; seek $fref, $r_len, 2; { my ($buffer,$pos); read $fref, $buffer, $r_len; $last = $buffer . $last; if (($pos = rindex($last,"\n",length($last)-2)) != -1) { return substr($last,$pos); } seek $fref, $r_len * 2, 1; redo; } }


    $_="goto+F.print+chop;\n=yhpaj";F1:eval
Re: end of file!
by lostsoul (Sexton) on Jul 04, 2007 at 09:14 UTC
    Is there any way to read a compressed (.gz) file backwards without decompressing the whole file to disk? I'm trying to read compressed logfiles.