21

I'm guessing it's fgets, but I can't find the specific syntax. I'm trying to read out (in a string I'm thinking is easier) the last line added to a log file.

waxical
  • 3,826
  • 8
  • 45
  • 69

10 Answers10

50

The simplest naive solution is simply:

$file = "/path/to/file";
$data = file($file);
$line = $data[count($data)-1];

Though, this WILL load the whole file into memory. Possibly a problem (or not). A better solution is this:

$file = escapeshellarg($file); // for the security concious (should be everyone!)
$line = `tail -n 1 $file`;
Matthew Scharley
  • 127,823
  • 52
  • 194
  • 222
  • 1
    Note that this is very unsafe unless you are using escapeshellarg(): http://de.php.net/manual/en/function.escapeshellarg.php – soulmerge Jun 30 '09 at 09:49
  • 8
    Correction, it's only unsafe if you're accepting user input as the file path (and then you should be doing all sorts of things to validate it). If you use a constant like in the example, then it's perfectly fine and you can save yourself a function call. Of course, it's probably not a bad idea anyway, just to get in the habit. – Matthew Scharley Jun 30 '09 at 09:53
  • I hate to harp on about this, but not as to start another question. When I get the last line and it's unique, it's fine. But, with this... when i get the last line and the few few parts of the line are identical to the last, it seems to think the lines are all part of one. Is this something you can avoid? – waxical Jun 30 '09 at 10:01
  • I'm not quite sure what you mean, but if I'm understanding you right, then that's really wierd, and I'd appreciate an example... perhaps starting a new question is a good idea? – Matthew Scharley Jun 30 '09 at 10:04
  • Will do. http://stackoverflow.com/questions/1062896/oddity-with-php-tail-n-1-returning-multiple-results – waxical Jun 30 '09 at 10:21
  • 1
    Extending your comment a bit further down ("If you are on a Mac...?") - What if you are on Windows and don't have the "tail" command? (I know there's a Win32 port of "tail", that's beside the point). Apart from that - even though I have not tested it I would not be surprised if creating a new process for every request does not scale too well. – Tomalak Jun 30 '09 at 12:42
  • You'd be surprised: PHP itself did this for a very long time till they made the SAPI modules, on slower, worse off servers than anything in public use today. – Matthew Scharley Jun 30 '09 at 13:46
  • Also, if I was running on Windows, I'd be using ASP. – Matthew Scharley Jun 30 '09 at 13:51
  • 1
    It would be nice if you include the rest of the code necessary on the `escapeshellarg` example. Creating a string does not magically get the last line of anything. – carter Jul 28 '14 at 20:04
  • 1
    @carter Nope; the shell out to `tail` does that. Nothing in life is magic, sadly. – Matthew Scharley Jul 31 '14 at 12:22
  • Thumbs down for the PHP example! This will consume all your memory with a large file. Must use fgetc()! – GTodorov Mar 29 '20 at 23:50
14

This looks like it is what you are looking for:

tekkie.flashbit.net: Tail functionality in PHP

It implements a function that uses fseek() with a negative index to roll up the file from the end. You can define how many lines you want to be returned.

The code also is available as a Gist on GitHub:

// full path to text file
define("TEXT_FILE", "/home/www/default-error.log");
// number of lines to read from the end of file
define("LINES_COUNT", 10);


function read_file($file, $lines) {
    //global $fsize;
    $handle = fopen($file, "r");
    $linecounter = $lines;
    $pos = -2;
    $beginning = false;
    $text = array();
    while ($linecounter > 0) {
        $t = " ";
        while ($t != "\n") {
            if(fseek($handle, $pos, SEEK_END) == -1) {
                $beginning = true; 
                break; 
            }
            $t = fgetc($handle);
            $pos --;
        }
        $linecounter --;
        if ($beginning) {
            rewind($handle);
        }
        $text[$lines-$linecounter-1] = fgets($handle);
        if ($beginning) break;
    }
    fclose ($handle);
    return array_reverse($text);
}

$fsize = round(filesize(TEXT_FILE)/1024/1024,2);

echo "<strong>".TEXT_FILE."</strong>\n\n";
echo "File size is {$fsize} megabytes\n\n";
echo "Last ".LINES_COUNT." lines of the file:\n\n";

$lines = read_file(TEXT_FILE, LINES_COUNT);
foreach ($lines as $line) {
    echo $line;
}
Gordon
  • 312,688
  • 75
  • 539
  • 559
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Iiinteresting... but utterly useless next to the much easier method of just shelling the issue over to tail (unless you really are on a very seriously overworked server) – Matthew Scharley Jun 30 '09 at 10:07
  • 11
    "utterly useless" is a bit harsh – Tomalak Jun 30 '09 at 10:17
  • Ok, perhaps a little... still, I'd be disinclined to use it, unless someone could prove to me that using `tail` was actually what was causing bottlenecks and not the operations on large databases. – Matthew Scharley Jun 30 '09 at 13:55
  • Now what have "operations on large databases" to do with the issue? If compare something then the performance of an efficient language native implementation vs. using the shell `tail ...`. ;-) – Tomalak Jun 30 '09 at 15:04
  • 2
    Well crud, now you're going to make me go benchmark them aren't you... 'cause I'm curious... – Matthew Scharley Jul 03 '09 at 09:58
  • 1
    I am, too. So *if* you benchmark it, I'd be interested in your findings. :-) Ideally this would be tested on a log file that is too large to fit into the file system cache. – Tomalak Jul 03 '09 at 10:06
  • 1
    utterly useful ;-) if you cannot depend on the availability of ´tail´ in shell, e.g. not UNIX or some shared hosting. The performance difference must be marginal since the php code efficiently uses `fseek` to read the file tail only. – PaulH May 08 '19 at 07:52
10
define('YOUR_EOL', "\n");
$fp = fopen('yourfile.txt', 'r');

$pos = -1; $line = ''; $c = '';
do {
    $line = $c . $line;
    fseek($fp, $pos--, SEEK_END);
    $c = fgetc($fp);
} while ($c != YOUR_EOL);

echo $line;

fclose($fp);

This is better, since it does not load the complete file into memory...

Set YOUR_EOL to your correct line endings, if you use the same line endings as the default line endings of the OS where your script resides, you could use the constant PHP_EOL.

5
function seekLastLine($f) {
    $pos = -2;
    do {
        fseek($f, $pos--, SEEK_END);
        $ch = fgetc($f);
    } while ($ch != "\n");
}

-2 because last char can be \n

kapa
  • 77,694
  • 21
  • 158
  • 175
constantined
  • 51
  • 1
  • 1
2

You either have to read the file in line by line and save the last read line to get it.

Or if on unix/linux you might consider using the shell command tail

tail -n 1 filename
jitter
  • 53,475
  • 11
  • 111
  • 124
1

If you want to read a file line by line the file function reads the contents of a file, line by line and returns each line as an element of an array.

So you could do something simple like:

$lines    = file('log.txt');
$lastLine = array_pop($lines);
NullUserException
  • 83,810
  • 28
  • 209
  • 234
Kieran Hall
  • 2,637
  • 2
  • 24
  • 27
  • 3
    Really? For a multi-megabyte log file of which 99.99% is of no interest to me I would try to avoid loading all of it into an array just to throw it away immediately. – Tomalak Jun 30 '09 at 09:44
  • No denying that this is inefficient, but it works; and who knows how long the file is? I would use the tail command in my environment, but WiseDonkey didn't specify any. That's a nice function you linked to, though. – Kieran Hall Jun 30 '09 at 09:52
  • 1
    file() and file_get_contents() are both great file manipulation functions, specially if you know the files involved are relatively small and just want to do something quickly and easily. – Matthew Scharley Jun 30 '09 at 09:56
  • @Matthew Scharley: The OP spoke of log files. These are the opposite of "relatively small" most of the time. – Tomalak Jun 30 '09 at 10:18
1

This one wont break for a 1 or 0 line file.

function readlastline($fileName)
{

       $fp = @fopen($fileName, "r");
       $begining = fseek($fp, 0);      
       $pos = -1;
       $t = " ";
       while ($t != "\n") {
             fseek($fp, $pos, SEEK_END);
             if(ftell($fp) == $begining){
              break;
             }
             $t = fgetc($fp);
             $pos = $pos - 1;
       }
       $t = fgets($fp);
       fclose($fp);
       return $t;
}
1

@unique_stephen, your answer is flawed. PHP fseek returns 0 for success and -1 for failure. Storing the result in $begining (sic) and then later using it in a filter for ftell() isn't correct. If my reputation were higher I would have voted you down and left a comment. Here is a modified version of unique_stephen's function.

function readlastline($fileName)
{
    $fp = @fopen($fileName, "r");
    if (fseek($fp, 0) == -1)
        exit('Cannot seek to beginning of the file'); 
    $pos = -1;
    $t = " ";
    while ($t != "\n") {
        if (fseek($fp, $pos, SEEK_END) == -1)
            exit('Cannot seek to the end of the file');
        if (ftell($fp) == 0) {
            break;
        }
        $t = fgetc($fp);
        $pos = $pos - 1;
    }
    $t = fgets($fp);
    fclose($fp);
    return $t;
}

NOTE: PHP's fseek cannot manage to seek to the end of files larger than PHP_MAX_INT which is 32bit signed even on 64bit binaries.

Earnie
  • 111
  • 1
  • 4
  • 1
    This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/low-quality-posts/18923154) – Aken Roberts Feb 24 '18 at 21:25
  • Yes, there are answers to the question already. The issue with not being able to leave a comment on an answer is what needs to be resolved if you don't want me to leave an answer as a comment. I will edit the answer to give a correct version of @unique_stephen's response. – Earnie Feb 27 '18 at 15:25
1
function readlastline($fileName)
{

       $fp = @fopen($fileName, "r");
       $begining = fseek($fp, 0);      
       $pos = -1;
       $t = " ";
       while ($t != "\n") {
             fseek($fp, $pos, SEEK_END);
             if(ftell($fp) == $begining){
              break;
             }
             $t = fgetc($fp);
             $pos = $pos - 1;
       }
       $t = fgets($fp);
       fclose($fp);
       return $t;
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
  • Welcome to SO! Please don't post code-only answers but add a little textual explanation about how and why your approach works and what makes it different from the other answers given. You can find out more at our ["How to write a good answer"](https://stackoverflow.com/help/how-to-answer) page. – ahuemmer Dec 30 '22 at 11:15
1

...Why just read the last line?

function readLines($fp, $num) {

    $line_count = 0; $line = ''; $pos = -1; $lines = array(); $c = '';

    while($line_count < $num) {
        $line = $c . $line; 
        fseek($fp, $pos--, SEEK_END);
        $c = fgetc($fp);
        if($c == "\n") { $line_count++; $lines[] = $line; $line = ''; $c = ''; }
    }   
    return $lines;
}

$filename = "content.txt";
$fp = @fopen($filename, "r");

print_r(readLines($fp, 2));

fclose($fp);