Php read last line of file

I've been bumping into a problem. I have a log on a Linux box in which is written the output from several running processes. This file can get really big sometimes and I need to read the last line from that file.

The problem is this action will be called via an AJAX request pretty often and when the file size of that log gets over 5-6MB it's rather not good for the server. So I'm thinking I have to read the last line but not to read the whole file and pass through it or load it in RAM because that would just load to death my box.

Is there any optimization for this operation so that it run smooth and not harm the server or kill Apache?

Other option that I have is to exec['tail -n 1 /path/to/log'] but it doesn't sound so good.

Later edit: I DO NOT want to put the file in RAM because it might get huge. fopen[] is not an option.

asked Oct 2, 2009 at 15:09

1

This should work:

$line = '';

$f = fopen['data.txt', 'r'];
$cursor = -1;

fseek[$f, $cursor, SEEK_END];
$char = fgetc[$f];

/**
 * Trim trailing newline chars of the file
 */
while [$char === "\n" || $char === "\r"] {
    fseek[$f, $cursor--, SEEK_END];
    $char = fgetc[$f];
}

/**
 * Read until the start of file or first newline char
 */
while [$char !== false && $char !== "\n" && $char !== "\r"] {
    /**
     * Prepend the new char
     */
    $line = $char . $line;
    fseek[$f, $cursor--, SEEK_END];
    $char = fgetc[$f];
}

fclose[$f];

echo $line;

Note that this solution will repeat the last character of the line unless your file ends in a newline. If your file does not end in a newline, you can change both instances of $cursor-- to --$cursor.

answered Oct 2, 2009 at 15:26

Ionuț G. StanIonuț G. Stan

172k18 gold badges187 silver badges199 bronze badges

9

Use fseek. You seek to the last position and seek it backward [use ftell to tell the current position] until you find a "\n".


$fp = fopen["....."];
fseek[$fp, -1, SEEK_END]; 
$pos = ftell[$fp];
$LastLine = "";
// Loop backword util "\n" is found.
while[[[$C = fgetc[$fp]] != "\n"] && [$pos > 0]] {
    $LastLine = $C.$LastLine;
    fseek[$fp, $pos--];
}
fclose[$fp];

NOTE: I've not tested. You may need some adjustment.

UPDATE: Thanks Syntax Error for pointing out about empty file.

:-D

UPDATE2: Fixxed another Syntax Error, missing semicolon at $LastLine = ""

Daan

7,4055 gold badges41 silver badges52 bronze badges

answered Oct 2, 2009 at 15:19

1

You're looking for the fseek function. There are working examples of how to read the last line of a file in the comments section there.

answered Oct 2, 2009 at 15:16

sliktsslikts

7,7871 gold badge27 silver badges45 bronze badges

this the code of Ionuț G. Stan

i modified your code a little and made it a function for reuseability

function read_last_line [$file_path]{



$line = '';

$f = fopen[$file_path, 'r'];
$cursor = -1;

fseek[$f, $cursor, SEEK_END];
$char = fgetc[$f];

/**
* Trim trailing newline chars of the file
*/
while [$char === "\n" || $char === "\r"] {
    fseek[$f, $cursor--, SEEK_END];
    $char = fgetc[$f];
}

/**
* Read until the start of file or first newline char
*/
while [$char !== false && $char !== "\n" && $char !== "\r"] {
    /**
     * Prepend the new char
     */
    $line = $char . $line;
    fseek[$f, $cursor--, SEEK_END];
    $char = fgetc[$f];
}

return $line;
}

echo read_last_line['log.txt'];

you will get that last line

answered Jun 30, 2014 at 5:09

If you know the upper bound of line length you could do something like this.

$maxLength = 1024;
$fp = fopen['somefile.txt', 'r'];
fseek[$fp, -$maxLength , SEEK_END]; 
$fewLines = explode["\n", fgets[$fp, $maxLength]];
$lastLine = $fewLines[count[$fewLines] - 1];

In response to the edit: fopen just acquires a handle to the file [i.e. make sure it exists, process has permission, lets os know a process is using the file, etc...]. In this example only 1024 characters from the file will be read into memory.

answered Oct 2, 2009 at 15:31

Lawrence BarsantiLawrence Barsanti

30.6k10 gold badges46 silver badges67 bronze badges

2

Your problem looks similar to this one

The best approach to avoid loading the whole file into memory seems to be:

$file = escapeshellarg[$file]; // for the security concious [should be everyone!]
$line = `tail -n 1 $file`;

answered Oct 2, 2009 at 15:17

James GoodwinJames Goodwin

7,2803 gold badges29 silver badges41 bronze badges

3

Would it be possible to optimize this from the other side? If so, just let the logging application always log the line to a file while truncating it [i.e. > instead of >>]

Some optimization might be achieved by "guessing" though, just open the file and with the average log line width you could guess where the last line would be. Jump to that position with fseek and find the last line.

answered Oct 2, 2009 at 15:12

WolphWolph

75.7k10 gold badges132 silver badges147 bronze badges

1

Untested code from the comments of //php.net/manual/en/function.fseek.php

jim at lfchosting dot com 05-Nov-2003 02:03
Here is a function that returns the last line of a file.  This should be quicker than reading the whole file till you get to the last line.  If you want to speed it up a bit, you can set the $pos = some number that is just greater than the line length.  The files I was dealing with were various lengths, so this worked for me. 

answered Mar 24, 2011 at 1:10

Syntax ErrorSyntax Error

4,3752 gold badges21 silver badges33 bronze badges

This is my solution with only one loop

        $line = '';
        $f = fopen[$file_path, 'r'];
        $cursor = 0 ;
        do  {
            fseek[$f, $cursor--, SEEK_END];
            $char = fgetc[$f];
            $line = $char.$line;
        } while [
                $cursor > -1 || [
                 ord[$char] !== 10 &&
                 ord[$char] !== 13
                ]
        ];

answered Jun 10, 2016 at 12:32

caiofiorcaiofior

3893 silver badges16 bronze badges

Here is a compilation of the answers here wrapped into a function which can specify how many lines should be returned.

function getLastLines[$path, $totalLines] {
  $lines = array[];

  $fp = fopen[$path, 'r'];
  fseek[$fp, -1, SEEK_END];
  $pos = ftell[$fp];
  $lastLine = "";

  // Loop backword until we have our lines or we reach the start
  while[$pos > 0 && count[$lines] < $totalLines] {

    $C = fgetc[$fp];
    if[$C == "\n"] {
      // skip empty lines
      if[trim[$lastLine] != ""] {
        $lines[] = $lastLine;
      }
      $lastLine = '';
    } else {
      $lastLine = $C.$lastLine;
    }
    fseek[$fp, $pos--];
  }

  $lines = array_reverse[$lines];

  return $lines;
}

answered Oct 3, 2016 at 16:00

DynamicDanDynamicDan

4034 silver badges12 bronze badges

This function will let you read last line or [optionally] entire file line-by-line from end, by passing $initial_pos which will tell from where to start reading a file from end [negative integer].

function file_read_last_line[$file, $initial_pos = -1] {
  $fp = is_string[$file] ? fopen[$file, 'r'] : $file;
  $pos = $initial_pos;
  $line = '';
  do {
    fseek[$fp, $pos, SEEK_END];
    $char = fgetc[$fp];
    if [$char === false] {
      if [$pos === $initial_pos] return false;
      break;
    }
    $pos = $pos - 1;
    if [$char === "\r" || $char === "\n"] continue;
    $line = $char . $line;
  } while [$char !== "\n"];
  if [is_string[$file]] fclose[$file];
  return $line;
}

Then, to read last line:

$last_line = file_read_last_line['/path/to/file'];

To read entire file line-by-line from end:

$fp = fopen['/path/to/file', 'r'];
$pos = -1;
while [[$line = file_read_last_line[$fp, $pos]] !== false] {
  $pos += -[strlen[$line] + 1];
  echo 'LINE: ' . $line . "\n";
}
fclose[$fp];

answered Jun 7, 2020 at 7:41

artnikproartnikpro

5,1354 gold badges35 silver badges38 bronze badges

1

Traverse backward a file chunk by chunk, stops after find a new line, and returns everything after the last "\n".

function get_last_line[$file] {
    if [!is_file[$file]] return false;
    $fileSize   = filesize[$file];  
    $bufferSize = 1024; //  $bufferSize] ? $bufferSize : $fileSize;

    $fp = fopen[$file, 'r'];
    
    $position = $fileSize - $bufferSize;
    $data = "";
    while [true] {
        fseek[$fp, $position];
        $chunk  = fread[$fp, $bufferSize];
        $data   = $chunk.$data;
        $pos    = strrchr[$data, "\n"];
        
        if [$pos !== false] return substr[$pos, 1];
        if [$position 

Chủ Đề