2011-06-23 18 views
11

¿Puedo leer un archivo en PHP de mi parte, por ejemplo si deseo leer las últimas 10-20 líneas?Lectura de archivos de gran tamaño desde el final

Y, como he leído, si el tamaño del archivo es más de 10mbs empiezo a recibir errores.

¿Cómo puedo evitar este error?

Para leer un archivo normal, se utiliza el código:

if ($handle) { 
    while (($buffer = fgets($handle, 4096)) !== false) { 
    $i1++; 
    $content[$i1]=$buffer; 
    } 
    if (!feof($handle)) { 
     echo "Error: unexpected fgets() fail\n"; 
    } 
    fclose($handle); 
} 

Mi archivo podría ir más 10Mbs, pero sólo hay que leer las últimas líneas. ¿Cómo lo hago?

Gracias

+0

Duplicado posible de: [PHP - leyendo desde el final de un archivo de texto] (http: // stackoverflow.com/questions/5697717) – hippietrail

Respuesta

5

no es pura PHP, pero la solución común es usar el comando tac que es la reversión de cat y carga el archivo en orden inverso. Use exec() o passthru() para ejecutarlo en el servidor y luego lea los resultados. Ejemplo de uso:

<?php 
$myfile = 'myfile.txt'; 
$command = "tac $myfile > /tmp/myfilereversed.txt"; 
exec($command); 
$currentRow = 0; 
$numRows = 20; // stops after this number of rows 
$handle = fopen("/tmp/myfilereversed.txt", "r"); 
while (!feof($handle) && $currentRow <= $numRows) { 
    $currentRow++; 
    $buffer = fgets($handle, 4096); 
    echo $buffer."<br>"; 
} 
fclose($handle); 
?> 
+0

+1 Bastante limpio, ¡estoy tan familiarizado con la línea de comandos! –

+0

¿Pero afecta el archivo real o simplemente el comando lo hace de manera virtual? – kritya

+0

No afecta el archivo real, pero crea un nuevo archivo /tmp/myfilereversed.txt, por lo que tendrá que eliminarlo después de todo – Greenisha

6

Depende de cómo interpreta "puede".

Si se pregunta si puede hacerlo directamente (con la función de PHP) sin leer todas las líneas anteriores, entonces la respuesta es: No, no puede.

Un final de línea es una interpretación de los datos y usted solo puede saber dónde están, si realmente lee los datos.

Si es un archivo realmente grande, no lo haría. Sería mejor si escanearas el archivo comenzando desde el final y leyeras bloques desde el final hasta el archivo.

actualización

Aquí hay una PHP sólo manera de leer los últimos n líneas de un archivo sin necesidad de leer a través de toda ella;

function last_lines($path, $line_count, $block_size = 512){ 
    $lines = array(); 

    // we will always have a fragment of a non-complete line 
    // keep this in here till we have our next entire line. 
    $leftover = ""; 

    $fh = fopen($path, 'r'); 
    // go to the end of the file 
    fseek($fh, 0, SEEK_END); 
    do{ 
     // need to know whether we can actually go back 
     // $block_size bytes 
     $can_read = $block_size; 
     if(ftell($fh) < $block_size){ 
      $can_read = ftell($fh); 
     } 

     // go back as many bytes as we can 
     // read them to $data and then move the file pointer 
     // back to where we were. 
     fseek($fh, -$can_read, SEEK_CUR); 
     $data = fread($fh, $can_read); 
     $data .= $leftover; 
     fseek($fh, -$can_read, SEEK_CUR); 

     // split lines by \n. Then reverse them, 
     // now the last line is most likely not a complete 
     // line which is why we do not directly add it, but 
     // append it to the data read the next time. 
     $split_data = array_reverse(explode("\n", $data)); 
     $new_lines = array_slice($split_data, 0, -1); 
     $lines = array_merge($lines, $new_lines); 
     $leftover = $split_data[count($split_data) - 1]; 
    } 
    while(count($lines) < $line_count && ftell($fh) != 0); 
    if(ftell($fh) == 0){ 
     $lines[] = $leftover; 
    } 
    fclose($fh); 
    // Usually, we will read too many lines, correct that here. 
    return array_slice($lines, 0, $line_count); 
} 
+0

Puede hacer esto sin leer todas las líneas anteriores, tal como lo sugirió usted en su última oración. :) – awgy

+0

@awgy: Lo que quise decir con directamente fue con una función PHP o ayuda del sistema operativo;) Tal vez lo redacté mal :) – phant0m

+0

@kritya, @awgy: He agregado una implementación de lo que describí. – phant0m

15

Puede usar fopen y fseek para navegar en el archivo hacia atrás desde el final. Por ejemplo

$fp = @fopen($file, "r"); 
$pos = -2; 
while (fgetc($fp) != "\n") { 
    fseek($fp, $pos, SEEK_END); 
    $pos = $pos - 1; 
} 
$lastline = fgets($fp); 
+0

Es la manera normal de leer -.- – kritya

+0

Al usar fseek con negativo offset y SEEK_END, configurará el indicador de posición para colocar $ offset bytes ** antes de ** fin del archivo, por lo que no necesita leer desde el principio del archivo – Greenisha

+0

Si el archivo termina en una nueva línea, este fragmento solo devuelve la nueva línea Además, creo que '$ pos' debe inicializarse en' -1' antes del inicio del ciclo. – awgy

2

Si su código no está funcionando e informa un error, ¡debe incluir el error en sus publicaciones!

La razón por la que recibe un error es porque está tratando de almacenar todo el contenido del archivo en el espacio de memoria de PHP.

La forma más eficaz de resolver el problema sería como sugiere Greenisha y busque el final del archivo y luego retroceda un poco. Pero el mecanismo de Greenisha para retroceder un poco no es muy eficiente.

Considere en cambio el método para obtener las últimas líneas de una secuencia (es decirdonde no se puede buscar):

while (($buffer = fgets($handle, 4096)) !== false) { 
    $i1++; 
    $content[$i1]=$buffer; 
    unset($content[$i1-$lines_to_keep]); 
} 

Así que si usted sabe que su máxima longitud de línea es 4096, entonces lo haría:

if (4096*lines_to_keep<filesize($input_file)) { 
    fseek($fp, -4096*$lines_to_keep, SEEK_END); 
} 

A continuación, aplicar el bucle he descrito anteriormente.

Puesto que C tiene algunos métodos más eficaces para tratar los flujos de bytes, la solución más rápida (en un POSIX/Unix/Linux/BSD) sistema sería simplemente:

$last_lines=system("last -" . $lines_to_keep . " filename"); 
+0

Solo una explicación más sería muy amable de tu parte, pensó +1 en la idea de desarmarlo. – kritya

+0

Su solución también recorre todo el archivo, excepto un poco más lento con la sobrecarga de fgets y fseek. – stefgosselin

+0

@stefgosselin: no - vuelva a leerlo - solo itera a través de un bloque al final del archivo que es más grande o del mismo tamaño que los datos que se extraerán. – symcbean

1

Aquí hay otra solución. No tiene control de longitud de línea en fgets(), puede agregarlo.

/* Read file from end line by line */ 
$fp = fopen(dirname(__FILE__) . '\\some_file.txt', 'r'); 
$lines_read = 0; 
$lines_to_read = 1000; 
fseek($fp, 0, SEEK_END); //goto EOF 
$eol_size = 2; // for windows is 2, rest is 1 
$eol_char = "\r\n"; // mac=\r, unix=\n 
while ($lines_read < $lines_to_read) { 
    if (ftell($fp)==0) break; //break on BOF (beginning...) 
    do { 
      fseek($fp, -1, SEEK_CUR); //seek 1 by 1 char from EOF 
     $eol = fgetc($fp) . fgetc($fp); //search for EOL (remove 1 fgetc if needed) 
     fseek($fp, -$eol_size, SEEK_CUR); //go back for EOL 
    } while ($eol != $eol_char && ftell($fp)>0); //check EOL and BOF 

    $position = ftell($fp); //save current position 
    if ($position != 0) fseek($fp, $eol_size, SEEK_CUR); //move for EOL 
    echo fgets($fp); //read LINE or do whatever is needed 
    fseek($fp, $position, SEEK_SET); //set current position 
    $lines_read++; 
} 
fclose($fp); 
-1

Como Einstein dijo que cada cosa debe hacerse lo más simple posible, pero no más simple. En este punto, necesita una estructura de datos, una estructura de datos LIFO o simplemente coloca una pila.

2

Para Linux que puede hacer

$linesToRead = 10; 
exec("tail -n{$linesToRead} {$myFileName}" , $content); 

obtendrá un conjunto de líneas en el contenido de la variable $

solución PHP puro

$f = fopen($myFileName, 'r'); 

    $maxLineLength = 1000; // Real maximum length of your records 
    $linesToRead = 10; 
    fseek($f, -$maxLineLength*$linesToRead, SEEK_END); // Moves cursor back from the end of file 
    $res = array(); 
    while (($buffer = fgets($f, $maxLineLength)) !== false) { 
     $res[] = $buffer; 
    } 

    $content = array_slice($res, -$linesToRead); 
0

bien durante la búsqueda de la Lo mismo, puedo decir lo siguiente y pensé que podría ser útil para otros, así que sharin g aquí:

/* archivo Leer desde la línea final de la línea */

function tail_custom($filepath, $lines = 1, $adaptive = true) { 
     // Open file 
     $f = @fopen($filepath, "rb"); 
     if ($f === false) return false; 

     // Sets buffer size, according to the number of lines to retrieve. 
     // This gives a performance boost when reading a few lines from the file. 
     if (!$adaptive) $buffer = 4096; 
     else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096)); 

     // Jump to last character 
     fseek($f, -1, SEEK_END); 

     // Read 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) != "\n") $lines -= 1; 

     // Start reading 
     $output = ''; 
     $chunk = ''; 

     // 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, "\n"); 

     } 

     // 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, "\n") + 1); 
     } 

     // Close file and return 
     fclose($f);  
     return trim($output); 

    } 
0

Si sabe acerca de cuánto tiempo las líneas son, puede evitar una gran cantidad de la magia negro y acaba de agarrar un trozo del final del archivo.

Necesitaba las últimas 15 líneas de un archivo de registro muy grande, y en total tenían unos 3000 caracteres. Así que cogí los últimos 8000 bytes para estar seguros, luego leí el archivo como siempre y tomé lo que necesitaba desde el final.

$fh = fopen($file, "r"); 
    fseek($fh, -8192, SEEK_END); 
    $lines = array(); 
    while($lines[] = fgets($fh)) {} 

Ésta es posiblemente incluso más eficiente que la respuesta más alta calificación, que lee el archivo de carácter por carácter, compara cada personaje, y una división basada en caracteres de nueva línea.

Cuestiones relacionadas