Leer la última línea del archivo


Me he estado topando con un problema. Tengo un registro en una caja de Linux en la que se escribe la salida de varios procesos en ejecución. Este archivo puede ser muy grande a veces y necesito leer la última línea de ese archivo.

El problema es que esta acción se llamará a través de una solicitud AJAX bastante a menudo y cuando el tamaño del archivo de ese registro supera los 5-6MB, no es bueno para el servidor. Así que estoy pensando que tengo que leer la última línea, pero no leer todo el archivo y pasar a través de él o cárgalo en RAM porque eso cargaría hasta la muerte mi caja.

¿Hay alguna optimización para esta operación para que se ejecute sin problemas y no dañe el servidor o mate a Apache?

Otra opción que tengo es exec('tail -n 1 /path/to/log') pero no suena tan bien.

Edición posterior: NO quiero poner el archivo en la RAM porque podría ser enorme. fopen() no es una opción.

Author: Lightness Races in Orbit, 2009-10-02

12 answers

Esto debería funcionar:

$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);
}

echo $line;
 41
Author: Ionuț G. Stan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-02 15:26:57

Use fseek. Busca la última posición y la busca hacia atrás (use ftell para indicar la posición actual) hasta que encuentre un "\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--);
}

NOTA: No he probado. Es posible que necesite algún ajuste.

ACTUALIZACIÓN: Gracias Syntax Error por señalar sobre el archivo vacío.

: - D

UPDATE2: Fixxed otro error de sintaxis, falta punto y coma en $LastLine = ""

 17
Author: NawaMan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-15 15:12:28

Estás buscando la función fseek. Hay ejemplos prácticos de cómo leer la última línea de un archivo en la sección de comentarios.

 5
Author: slikts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-02 15:16:24

Si conoces el límite superior de la longitud de la línea podrías hacer algo como esto.

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

En respuesta a la edición : fopen solo adquiere un identificador para el archivo (es decir, asegúrese de que existe, el proceso tiene permiso, le permite al sistema operativo saber que un proceso está usando el archivo, etc.)...). En este ejemplo solo se leerán en memoria 1024 caracteres del archivo.

 3
Author: Lawrence Barsanti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-02 16:25:15
function readlastline() 
{ 
       $fp = @fopen("/dosmnt/LOGFILE.DAT", "r"); 
       $pos = -1; 
       $t = " "; 
       while ($t != "\n") { 
             fseek($fp, $pos, SEEK_END); 
             $t = fgetc($fp); 
             $pos = $pos - 1; 
       } 
       $t = fgets($fp); 
       fclose($fp); 
       return $t; 
} 

Fuente: http://forums.devshed.com/php-development-5/php-quick-way-to-read-last-line-156010.html

 3
Author: Daniel A. White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-24 01:09:40

Este es el código de Ionuț G. Stan

Modifiqué un poco tu código y lo convertí en una función para 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');

Obtendrá la última línea

 2
Author: Abdalla Mohamed Aly Ibrahim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-30 05:09:50

Su problema es similar a este

El mejor enfoque para evitar cargar todo el archivo en la memoria parece ser:

$file = escapeshellarg($file); // for the security concious (should be everyone!)
$line = `tail -n 1 $file`;
 1
Author: James Goodwin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:09:45

¿Sería posible optimizar esto desde el otro lado? Si es así, simplemente deje que la aplicación de registro registre siempre la línea en un archivo mientras la trunca (es decir, > en lugar de>>)

Sin embargo, se puede lograr alguna optimización "adivinando", simplemente abra el archivo y con el ancho promedio de la línea de registro podría adivinar dónde estaría la última línea. Salta a esa posición con fseek y encuentra la última línea.

 0
Author: Wolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-02 15:12:37

Código no probado de los comentarios de http://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. 

<?php 
function readlastline($file) 
{ 
        $fp = @fopen($file, "r"); 
        $pos = -1; 
        $t = " "; 
        while ($t != "\n") { 
              fseek($fp, $pos, SEEK_END); 
              $t = fgetc($fp); 
              $pos = $pos - 1; 
        } 
        $t = fgets($fp); 
        fclose($fp); 
        return $t; 
} 
?>
 0
Author: Syntax Error,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-24 01:10:57

Esta es mi solución con un solo bucle

        $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
                )
        );
 0
Author: caiofior,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-10 12:32:56

Aquí hay una compilación de las respuestas aquí envueltas en una función que puede especificar cuántas líneas deben devolverse.

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;
}
 0
Author: DynamicDan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-03 16:00:19

Usaría file() que lee el archivo en un array, invertiría el array y obtenía el primer elemento o pop el array:

L last_line = array_pop (file (fil filename));

Si desea rendimiento, intente abrir el archivo y usar el puntero del archivo para navegar hacia él.

 -3
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-02 15:35:01