tags:

views:

365

answers:

7

hello

i have a file named "file.txt" it updates by adding lines to it.

I am reading it by this code:

$fp = fopen("file.txt", "r");
$data = "";
while(!feof($fp))
{
$data .= fgets($fp, 4096);
}
echo $data;

and a huge number of lines appears. I just want to echo the last 5 lines of the file

how can i do that ?

thanks in advanced


edit: @Alistair

the file.txt is like this

1111111111111122222222222

3333333333333344444444444

5555555555555566666666666

A: 

If your lines are separated by a CR or LF you would try exploding your $data variable:

$lines = explode("\n", $data);

$lines should end up being an array and you can work out the number of records using sizeof() and just get the last 5.

Alistair
i editted the Q, What can I do?
safaali
+1  A: 

Untested code, but should work:

$file = file("filename.txt");
for ($i = count($file)-6; $i < count($file); $i++) {
  echo $file[$i] . "\n";
}

This is the bare minimum that does the trick. In your real code you should also check count($file) to make sure it has at least five rows, to avoid errors.

Maerlyn
what if filename.txt only contains 3 lines?
thetaiko
thanks, that worked, it is more than 10 lines every time:))
safaali
@thetaiko indeed, I updated the answer with a note regarding that.
Maerlyn
+2  A: 
function ReadFromEndByLine($filename,$lines)
{

        /* freely customisable number of lines read per time*/
        $bufferlength = 5000;

        $handle = @fopen($filename, "r");
        if (!$handle) {
                echo "Error: can't find or open $filename<br/>\n";
                return -1;
        }

        /*get the file size with a trick*/
        fseek($handle, 0, SEEK_END);
        $filesize = ftell($handle);

        /*don't want to get past the start-of-file*/
        $position= - min($bufferlength,$filesize);

        while ($lines > 0) {

                if ($err=fseek($handle,$position,SEEK_END)) {  /* should not happen but it's better if we check it*/
                        echo "Error $err: something went wrong<br/>\n";
                        fclose($handle);
                        return $lines;
                }

                /* big read*/
                $buffer = fread($handle,$bufferlength);

                /* small split*/
                $tmp = explode("\n",$buffer);

                /*previous read could have stored a partial line in $aliq*/
                if ($aliq != "") {

                                /*concatenate current last line with the piece left from the previous read*/
                                $tmp[count($tmp)-1].=$aliq;
                }

                /*drop first line because it may not be complete*/
                $aliq = array_shift($tmp);

                $read = count($tmp);
                if ( $read >= $lines ) {   /*have read too much!*/

                        $tmp2 = array_slice($tmp,$read-$n);
                        /* merge it with the array which will be returned by the function*/
                        $lines = array_merge($tmp2,$lines);

                        /* break the cycle*/
                        $lines = 0;
                } elseif (-$position >= $filesize) {  /* haven't read enough but arrived at the start of file*/

                        //get back $aliq which contains the very first line of the file
                        $lines = array_merge($aliq,$tmp,$lines);

                        //force it to stop reading
                        $lines = 0;

                } else {              /*continue reading...*/

                        //add the freshly grabbed lines on top of the others
                        $lines = array_merge($tmp,$lines);

                        $lines -= $read;

                        //next time we want to read another block
                        $position -= $bufferlength;

                        //don't want to get past the start of file
                        $position = max($position, -$filesize);
                }
        }
        fclose($handle);

        return $lines;
}

This will be fast for larger files but alot of code for a simple task, if there LARGE FILES, use this

ReadFromEndByLine('myFile.txt',6);

RobertPitt
This requires reading the entire file into memory, which may be very bad.
dkamins
Updated with a larger block of code but yet faster and less memory usage. - Taken from http://mydebian.blogdns.org/?p=197
RobertPitt
That seems really bloated an unnecessarily large for such a simple operation.
Lotus Notes
I like your solution, and that's the way I've been told to write my "tail" function. +1 :-)
Aif
+5  A: 

For a large file, reading all the lines into an array with file() is a bit wasteful. Here's how you could read the file and maintain a buffer of the last 5 lines:

$lines=array();
$fp = fopen("file.txt", "r");
while(!feof($fp))
{
   $line = fgets($fp, 4096);
   array_push($lines, $line);
   if (count($lines)>5)
       array_shift($lines);
}
fclose($fp);

You could optimize this a bit more with some heuristics about likely line length by seeking to a position, say, approx 10 lines from the end, and going further back if that doesn't yield 5 lines. Here's a simple implementation which demonstrates that:

//how many lines?
$linecount=5;

//what's a typical line length?
$length=40;

//which file?
$file="test.txt";

//we double the offset factor on each iteration
//if our first guess at the file offset doesn't
//yield $linecount lines
$offset_factor=1;


$bytes=filesize($file);

$fp = fopen($file, "r") or die("Can't open $file");


$complete=false;
while (!$complete)
{
    //seek to a position close to end of file
    $offset = $linecount * $length * $offset_factor;
    fseek($fp, -$offset, SEEK_END);


    //we might seek mid-line, so read partial line
    //if our offset means we're reading the whole file, 
    //we don't skip...
    if ($offset<$bytes)
        fgets($fp);

    //read all following lines, store last x
    $lines=array();
    while(!feof($fp))
    {
        $line = fgets($fp);
        array_push($lines, $line);
        if (count($lines)>$linecount)
        {
            array_shift($lines);
            $complete=true;
        }
    }

    //if we read the whole file, we're done, even if we
    //don't have enough lines
    if ($offset>=$bytes)
        $complete=true;
    else
        $offset_factor*=2; //otherwise let's seek even further back

}
fclose($fp);

var_dump($lines);
Paul Dixon
thanks man ......
safaali
+1  A: 

PHP's file() function reads the whole file into an array. This solution requires the least amount of typing:

$data = array_slice(file('file.txt'), -5);

foreach ($data as $line) {
    echo $line;
}
Lotus Notes
A: 

If you're on a linux system you could do this:

$lines = `tail -5 /path/to/file.txt`;

Otherwise you'll have to count lines and take the last 5, something like:

$all_lines = file('file.txt');
$last_5 = array_slice($all_lines , -5);
Rob
+1  A: 

This is a common interview question. Here's what I wrote last year when I was asked this question. Remember that code you get on Stack Overflow is licensed with the Creative Commons Share-Alike with attribution required.

<?php

/**
 * Demonstrate an efficient way to search the last 100 lines of a file
 * containing roughly ten million lines for a sample string. This should
 * function without having to process each line of the file (and without making
 * use of the “tail” command or any external system commands). 
 */

$filename = '/opt/local/apache2/logs/karwin-access_log';
$searchString = 'index.php';
$numLines = 100;
$maxLineLength = 200;

$fp = fopen($filename, 'r');

$data = fseek($fp, -($numLines * $maxLineLength), SEEK_END);

$lines = array();
while (!feof($fp)) {
  $lines[] = fgets($fp);
}

$c = count($lines);
$i = $c >= $numLines? $c-$numLines: 0;
for (; $i<$c; ++$i) {
  if ($pos = strpos($lines[$i], $searchString)) {
    echo $lines[$i];
  }
}

This solution does make an assumption about the maximum line length. The interviewer asked me how I would solve the problem if I couldn't make that assumption, and had to accommodate lines that were potentially longer than any max length I chose.

I told him that any software project has to make certain assumptions, but I could test if $c was less than the desired number of lines, and if it isn't, fseek() back further incrementally (doubling each time) until we do get enough lines.

Bill Karwin