web-dev-qa-db-fra.com

Comment lire seulement 5 dernière ligne du fichier texte en PHP?

J'ai un fichier nommé "fichier.txt" Il se met à jour en y ajoutant des lignes. 

Je le lis par ce code:

$fp = fopen("file.txt", "r");
$data = "";
while(!feof($fp))
{
$data .= fgets($fp, 4096);
}
echo $data;

et un grand nombre de lignes apparaît. Je veux juste faire écho aux 5 dernières lignes du fichier 

Comment puis je faire ça ?


Le file.txt est comme ceci:

11111111111111
22222222222

33333333333333
44444444444

55555555555555
66666666666
28
Alireza

Code non testé, mais devrait fonctionner:

$file = file("filename.txt");
for ($i = max(0, count($file)-6); $i < count($file); $i++) {
  echo $file[$i] . "\n";
}

L'appel de max traitera le fichier de moins de 6 lignes.

19
Maerlyn

Pour un fichier volumineux, lire toutes les lignes d'un tableau avec file () est un peu inutile. Voici comment vous pouvez lire le fichier et conserver un tampon des 5 dernières lignes:

$lines=array();
$fp = fopen("file.txt", "r");
while(!feof($fp))
{
   $line = fgets($fp, 4096);
   array_Push($lines, $line);
   if (count($lines)>5)
       array_shift($lines);
}
fclose($fp);

Vous pouvez optimiser cela un peu plus avec quelques heuristiques sur la longueur de ligne probable en cherchant une position, disons, à environ 10 lignes de la fin et en remontant plus loin si cela ne donne pas 5 lignes. Voici une implémentation simple qui démontre que:

//how many lines?
$linecount=5;

//what's a typical line length?
$length=40;

//which file?
$file="test.txt";

//we double the offset factor on each iteration
//if our first guess at the file offset doesn't
//yield $linecount lines
$offset_factor=1;


$bytes=filesize($file);

$fp = fopen($file, "r") or die("Can't open $file");


$complete=false;
while (!$complete)
{
    //seek to a position close to end of file
    $offset = $linecount * $length * $offset_factor;
    fseek($fp, -$offset, SEEK_END);


    //we might seek mid-line, so read partial line
    //if our offset means we're reading the whole file, 
    //we don't skip...
    if ($offset<$bytes)
        fgets($fp);

    //read all following lines, store last x
    $lines=array();
    while(!feof($fp))
    {
        $line = fgets($fp);
        array_Push($lines, $line);
        if (count($lines)>$linecount)
        {
            array_shift($lines);
            $complete=true;
        }
    }

    //if we read the whole file, we're done, even if we
    //don't have enough lines
    if ($offset>=$bytes)
        $complete=true;
    else
        $offset_factor*=2; //otherwise let's seek even further back

}
fclose($fp);

var_dump($lines);
40
Paul Dixon
function ReadFromEndByLine($filename,$lines)
{

        /* freely customisable number of lines read per time*/
        $bufferlength = 5000;

        $handle = @fopen($filename, "r");
        if (!$handle) {
                echo "Error: can't find or open $filename<br/>\n";
                return -1;
        }

        /*get the file size with a trick*/
        fseek($handle, 0, SEEK_END);
        $filesize = ftell($handle);

        /*don't want to get past the start-of-file*/
        $position= - min($bufferlength,$filesize);

        while ($lines > 0) {

                if ($err=fseek($handle,$position,SEEK_END)) {  /* should not happen but it's better if we check it*/
                        echo "Error $err: something went wrong<br/>\n";
                        fclose($handle);
                        return $lines;
                }

                /* big read*/
                $buffer = fread($handle,$bufferlength);

                /* small split*/
                $tmp = explode("\n",$buffer);

                /*previous read could have stored a partial line in $aliq*/
                if ($aliq != "") {

                                /*concatenate current last line with the piece left from the previous read*/
                                $tmp[count($tmp)-1].=$aliq;
                }

                /*drop first line because it may not be complete*/
                $aliq = array_shift($tmp);

                $read = count($tmp);
                if ( $read >= $lines ) {   /*have read too much!*/

                        $tmp2 = array_slice($tmp,$read-$n);
                        /* merge it with the array which will be returned by the function*/
                        $lines = array_merge($tmp2,$lines);

                        /* break the cycle*/
                        $lines = 0;
                } elseif (-$position >= $filesize) {  /* haven't read enough but arrived at the start of file*/

                        //get back $aliq which contains the very first line of the file
                        $lines = array_merge($aliq,$tmp,$lines);

                        //force it to stop reading
                        $lines = 0;

                } else {              /*continue reading...*/

                        //add the freshly grabbed lines on top of the others
                        $lines = array_merge($tmp,$lines);

                        $lines -= $read;

                        //next time we want to read another block
                        $position -= $bufferlength;

                        //don't want to get past the start of file
                        $position = max($position, -$filesize);
                }
        }
        fclose($handle);

        return $lines;
}

Ce sera rapide pour les gros fichiers mais beaucoup de code pour une tâche simple, si il y a de gros fichiers, utilisez ceci

ReadFromEndByLine ('myFile.txt', 6);

13
RobertPitt

Si vous êtes sur un système Linux, vous pouvez faire ceci:

$lines = `tail -5 /path/to/file.txt`;

Sinon, vous devrez compter les lignes et prendre les 5 dernières, quelque chose comme:

$all_lines = file('file.txt');
$last_5 = array_slice($all_lines , -5);
10
Rob

Ceci est une question d'entrevue commune. Voici ce que j'ai écrit l'année dernière quand on m'a posé cette question. Rappelez-vous que le code que vous obtenez sur Stack Overflow est concédé sous licence avec le fichier Creative Commons Share-Alike avec attribution requis .

<?php

/**
 * Demonstrate an efficient way to search the last 100 lines of a file
 * containing roughly ten million lines for a sample string. This should
 * function without having to process each line of the file (and without making
 * use of the “tail” command or any external system commands). 
 */

$filename = '/opt/local/Apache2/logs/karwin-access_log';
$searchString = 'index.php';
$numLines = 100;
$maxLineLength = 200;

$fp = fopen($filename, 'r');

$data = fseek($fp, -($numLines * $maxLineLength), SEEK_END);

$lines = array();
while (!feof($fp)) {
  $lines[] = fgets($fp);
}

$c = count($lines);
$i = $c >= $numLines? $c-$numLines: 0;
for (; $i<$c; ++$i) {
  if ($pos = strpos($lines[$i], $searchString)) {
    echo $lines[$i];
  }
}

Cette solution suppose une longueur maximale de ligne. L'intervieweur m'a demandé comment je résoudrais le problème si je ne pouvais pas faire cette hypothèse et que je devais prendre en charge des lignes potentiellement plus longues que la longueur maximale que je choisissais. 

Je lui ai dit que tout projet logiciel devait faire certaines hypothèses, mais je pouvais vérifier si $c était inférieur au nombre de lignes souhaité, et si ce n'était pas le cas, fseek() de nouveau incrémentiel (doublant à chaque fois) jusqu'à ce que nous obtenions suffisamment de lignes .

7
Bill Karwin

Ceci n'utilise pas file(), donc ce sera plus efficace pour les gros fichiers;

<?php
function read_backward_line($filename, $lines, $revers = false)
{
    $offset = -1;
    $c = '';
    $read = '';
    $i = 0;
    $fp = @fopen($filename, "r");
    while( $lines && fseek($fp, $offset, SEEK_END) >= 0 ) {
        $c = fgetc($fp);
        if($c == "\n" || $c == "\r"){
            $lines--;
            if( $revers ){
                $read[$i] = strrev($read[$i]);
                $i++;
            }
        }
        if( $revers ) $read[$i] .= $c;
        else $read .= $c;
        $offset--;
    }
    fclose ($fp);
    if( $revers ){
        if($read[$i] == "\n" || $read[$i] == "\r")
            array_pop($read);
        else $read[$i] = strrev($read[$i]);
        return implode('',$read);
    }
    return strrev(rtrim($read,"\n\r"));
}
//if $revers=false function return->
//line 1000: i am line of 1000
//line 1001: and i am line of 1001
//line 1002: and i am last line
//but if $revers=true function return->
//line 1002: and i am last line
//line 1001: and i am line of 1001
//line 1000: i am line of 1000
?>
5
dukevin

La plupart des options ici supposent de lire le fichier dans la mémoire, puis de travailler avec des lignes. Ce ne serait pas une bonne idée si le fichier était trop volumineux

Je pense que le meilleur moyen est d'utiliser un utilitaire OS, comme 'tail' dans Unix.

exec('tail -3 /logs/reports/2017/02-15/173606-arachni-2415.log', $output);
echo $output;

// 2017-02-15 18:03:25 [*] Path Traversal: Analyzing response ...
// 2017-02-15 18:03:27 [*] Path Traversal: Analyzing response ...
// 2017-02-15 18:03:27 [*] Path Traversal: Analyzing response ...
4
Anton

L'ouverture de fichiers volumineux avec file() peut générer un grand tableau, en réservant une quantité considérable de mémoire.

Vous pouvez réduire le coût de la mémoire avec SplFileObject car il effectue une itération sur chaque ligne.

Utilisez la méthode seek (of seekableiterator) pour récupérer la dernière ligne. Vous devez alors soustraire la valeur actuelle de la clé par 5.

Pour obtenir la dernière ligne, utilisez PHP_INT_MAX. (Oui, ceci est une solution de contournement.)

$file = new SplFileObject('large_file.txt', 'r');

$file->seek(PHP_INT_MAX);

$last_line = $file->key();

$lines = new LimitIterator($file, $last_line - 5, $last_line);

print_r(iterator_to_array($lines));
3
Wallace Maxters

La fonction file () de PHP lit l'intégralité du fichier dans un tableau . Cette solution nécessite le moins de frappe possible:

$data = array_slice(file('file.txt'), -5);

foreach ($data as $line) {
    echo $line;
}
3
Lotus Notes

Cette fonction fonctionnera pour les très gros fichiers de moins de 4 Go. La vitesse provient de la lecture d'un gros bloc de données au lieu d'un octet à la fois et du comptage de lignes.

// Will seek backwards $n lines from the current position
function seekLineBackFast($fh, $n = 1){
    $pos = ftell($fh);
    if ($pos == 0)
        return false;

    $posAtStart = $pos;

    $readSize = 2048*2;
    $pos = ftell($fh);
    if(!$pos){
            fseek($fh, 0, SEEK_SET);
            return false;
    }

    // we want to seek 1 line before the line we want.
    // so that we can start at the very beginning of the line
    while ($n >= 0) {
        if($pos == 0)
                    break;
            $pos -= $readSize;
            if($pos <= 0){
                    $pos = 0;
            }

            // fseek returns 0 on success and -1 on error
            if(fseek($fh, $pos, SEEK_SET)==-1){
                    fseek($fh, 0, SEEK_SET);
                    break;
            }
            $data = fread($fh, $readSize);
            $count = substr_count($data, "\n");
            $n -= $count;

            if($n < 0)
                    break;
    }
    fseek($fh, $pos, SEEK_SET);
    // we may have seeked too far back
    // so we read one line at a time forward
    while($n < 0){
            fgets($fh);
            $n++;
    }
    // just in case?
    $pos = ftell($fh);
    if(!$pos)
        fseek($fh, 0, SEEK_SET);

    // check that we have indeed gone back
    if ($pos >= $posAtStart)
        return false;

    return $pos;
}

Après avoir exécuté la fonction ci-dessus, vous pouvez simplement créer une boucle avec fgets () pour lire chaque ligne à la fois à partir de $ fh.

2
over_optimistic

Vous pouvez utiliser ma petite bibliothèque d'aide (2 fonctions) 

https://github.com/jasir/file-helpers

Ensuite, utilisez simplement:

//read last 5 lines
$lines = \jasir\FileHelpers\FileHelpers::readLastLines($pathToFile, 5);
1
jasir
$dosya = "../dosya.txt";
$array = explode("\n", file_get_contents($dosya));
$reversed = array_reverse($array);
for($x = 0; $x < 6; $x++) 
{
    echo $reversed[$x];
}

Voici ma solution:

/**
 *
 * Reads N lines from a file
 *
 * @param type $file       path
 * @param type $maxLines   Count of lines to read
 * @param type $reverse    set to true if result should be reversed.
 * @return string
 */
public function readLinesFromFile($file, $maxLines, $reverse=false)
{
    $lines = file($file);

    if ($reverse) {
        $lines = array_reverse($lines);
    }

    $tmpArr = array();

    if ($maxLines > count($lines))
        exit("\$maxLines ist größer als die Anzahl der Zeilen in der Datei.");

    for ($i=0; $i < $maxLines; $i++) {
        array_Push($tmpArr, $lines[$i]);
    }

    if ($reverse) {
        $tmpArr = array_reverse($tmpArr);
    }

    $out = "";
    for ($i=0; $i < $maxLines; $i++) {
        $out .= $tmpArr[$i] . "</br>";
    }

    return $out;
}
0
Black

J'ai testé celui-ci. Ça marche pour moi.

function getlast($filename,$linenum_to_read,$linelength){

   // this function takes 3 arguments;


   if (!$linelength){ $linelength = 600;}
$f = fopen($filename, 'r');
$linenum = filesize($filename)/$linelength;

    for ($i=1; $i<=($linenum-$linenum_to_read);$i++) {
    $data = fread($f,$linelength);
    }
echo "<pre>";       
    for ($j=1; $j<=$linenum_to_read+1;$j++) {
    echo fread($f,$linelength);
    }

echo "</pre><hr />The filesize is:".filesize("$filename");
}

getlast("file.txt",6,230);


?>
0
Kaibo

La moindre quantité de bélier et produit bien. Je suis d'accord avec Paul Dixon ... 

$lines=array();
$fp = fopen("userlog.txt", "r");
while(!feof($fp))
{
 $line = fgets($fp, 4096);
 array_Push($lines, $line);
 if (count($lines)>25)
   array_shift($lines);
}
fclose($fp);

while ($a <= 10) {
$a++;
echo "<br>".$lines[$a];
}
0
Mikeys4u