PHP: Tail tackling large files
Published:
Needed a function that could get me the last N lines of a log file. Wanted it to be efficient and not dependent on anything other than my code.
Found some versions, but they were either a bit messy or depended on unstable arithmetic (where filesize is greater than PHP_INT_MAX
). So, I decided to take on the challenge and try to write one myself. Nice little exercise 🙂
define('TAIL_NL', "\n");
/**
* Tail in PHP, capable of eating big files.
*
* @author Torleif Berger
* @link http://www.geekality.net/?p=1654
*/
function tail($filename, $lines = 10, $buffer = 4096)
{
// Open the file
if(is_resource($file) && (get_resource_type($file) == 'file'
|| get_resource_type($file) == 'stream'))
$f = $file;
elseif(is_string($file))
$f = fopen($file, 'rb');
else
throw new Exception('$file must be either a resource (file or stream) or a filename.');
// Jump to last character
fseek($f, -1, SEEK_END);
// Prepare to collect output
$output = '';
$chunk = '';
// Start reading it and adjust line number if necessary
// (Otherwise the result would be wrong if file doesn't end with a blank line)
if(fread($f, 1) != TAIL_NL) $lines -= 1;
// While we would like more
while(ftell($f) > 0 && $lines >= 0)
{
// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$output = ($chunk = fread($f, $seek)).$output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, TAIL_NL);
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while($lines++ < 0)
{
// Find first newline and remove all text before that
$output = substr($output, strpos($output, TAIL_NL) + 1);
}
// Close file and return
fclose($f);
return $output;
}