Skip to content

Instantly share code, notes, and snippets.

@gbhorwood
Last active July 22, 2025 15:17
Show Gist options
  • Select an option

  • Save gbhorwood/865c3e85fbff170ce659963d7ff72f6c to your computer and use it in GitHub Desktop.

Select an option

Save gbhorwood/865c3e85fbff170ce659963d7ff72f6c to your computer and use it in GitHub Desktop.
Pure php implementation of tail -f
<?php
/**
* Tailf
* A pure PHP implementation of the linux command tail -f
*
* 20250722 gbh
*/
// duration for usleep
define('ONE_TENTH_SECOND', 100000);
// number of lines to output on tail
define('TAIL_LINE_COUNT', 10);
/**
* Entry point
*/
tailf(filePath($argv));
/**
* Gets file path from cli arguments, tests for validity
* and returns it.
* Dies with message on any fail.
*
* @param array<string> $argv
* @return string
*/
function filePath($argv): string {
$file = $argv[1] ?? null;
if(!$file) {
die("Must pass a file as argument");
}
if(!file_exists($file)) {
die("No file at '$file'");
}
if(!is_readable($file)) {
die("Cannot read file at '$file'");
}
return $file;
}
/**
* Opens a file, returns the pointer resource
*
* @param string $file Path to file
* @return mixed The file pointer resource
*/
function open(string $file): mixed {
$fp = fopen($file, "r");
return $fp;
}
/**
* Updates a file pointer $fp to be n bytes from the end of the file
* so that outputting to eof prints the last $lineCount lines.
*
* @param mixed $fp The file pointer resource
* @param int $lineCount How many lines back from the end of file
* @return mixed The file pointer resource
*/
function windToTailStart(mixed $fp, int $lineCount): mixed {
fseek($fp, 0, SEEK_END);
$position = -1;
$lineCounter = 0;
do {
if(ftell($fp) == 0) {
return $fp;
}
fseek($fp, $position--, SEEK_END);
if(fgetc($fp) == PHP_EOL) {
$lineCounter++;
}
}
while($lineCounter < $lineCount);
return $fp;
}
/**
* Outputs all lines of file from the position of the
* file pointer $fp to the end of file.
*
* @param mixed $fp The file pointer resource
* @return void
*/
function output(mixed $fp) {
while(!feof($fp)) {
print fgets($fp);
}
}
/**
* Outputs the last $lineCount lines from file $file.
*
* @param string $file Path to file
* @param int $lineCount
* @return void
*/
function tail(string $file, int $lineCount) {
$fp = open($file);
$fp = windToTailStart($fp, $lineCount);
output($fp);
fclose($fp);
}
/**
* Outputs the last $lineCount lines from file $file
* then waits for the file to update and outputs the
* new lines. ie. `tail -f`
*
* @param string $file Path to file
* @param int $lineCount
* @return void
*/
function tailf(string $file, int $lineCount = TAIL_LINE_COUNT) {
// output tail content
tail($file, $lineCount);
// open file at the end
$fp = open($file);
fseek($fp, 0, SEEK_END);
// wait for new file content and output
while(true) { /** @phpstan-ignore while.alwaysTrue */
$tell = ftell($fp);
usleep(ONE_TENTH_SECOND);
fseek($fp, 0, SEEK_END);
if($tell != ftell($fp)) {
fseek($fp, $tell, SEEK_SET);
output($fp);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment