代码之家  ›  专栏  ›  技术社区  ›  Alireza

如何在PHP中只读取文本文件的最后5行?

  •  28
  • Alireza  · 技术社区  · 16 年前

    我有一个名为“file.txt”的文件 它通过添加行来更新。

    我读这段代码:

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

    出现了大量的线条。 我只想重复文件的最后5行

    我该怎么做?


    这个 file.txt 是这样的:

    11111111111111
    22222222222
    
    33333333333333
    44444444444
    
    55555555555555
    66666666666
    
    17 回复  |  直到 8 年前
        1
  •  19
  •   Omar Alshaker    10 年前

    未测试的代码,但应该有效:

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

    打电话 max 将处理少于6行的文件。

        2
  •  40
  •   Paul Dixon    16 年前

    对于大型文件,使用file()将所有行读取到数组中有点浪费。以下是如何读取文件并维护最后5行的缓冲区:

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

    您可以通过一些关于可能的行长度的启发式方法对此进行更多优化,方法是寻找一个位置,例如,从末尾开始大约10行,如果不产生5行,则返回更远的位置。下面是一个简单的实现,它演示了:

    //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);
    
        3
  •  13
  •   RobertPitt    16 年前
    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;
    }
    

    对于较大的文件,这会很快,但对于简单的任务来说,代码太多,如果有较大的文件,请使用这个

    readfromendbyline('myfile.txt',6);

        4
  •  10
  •   Rob    16 年前

    如果您使用的是Linux系统,可以这样做:

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

    否则,您必须计算行数并取最后5行,如下所示:

    $all_lines = file('file.txt');
    $last_5 = array_slice($all_lines , -5);
    
        5
  •  7
  •   Bill Karwin    16 年前

    这是一个常见的面试问题。这是我去年被问到这个问题时写的。记住,您在堆栈溢出上获得的代码是使用 Creative Commons Share-Alike 具有 attribution required .

    <?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];
      }
    }
    

    这个解决方案确实假设了最大行长度。面试官问我,如果我不能做出这样的假设,我将如何解决这个问题,并且必须适应可能比我选择的任何最大长度更长的线条。

    我告诉他任何软件项目都必须做出某些假设,但我可以测试 $c 小于所需的行数,如果不是, fseek() 以递增的方式后退(每次翻倍),直到我们得到足够的线路。

        6
  •  5
  •   dukevin    14 年前

    这个不用了 file() 因此,对于大型文件来说,它将更加高效;

    <?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
    ?>
    
        7
  •  4
  •   Anton    9 年前

    这里的大多数选项都假定将文件读取到内存中,然后使用行。如果文件太大,这不是个好主意

    我认为最好的方法是使用一些操作系统实用程序,比如Unix中的“tail”。

    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 ...
    
        8
  •  3
  •   Lotus Notes    16 年前

    PHP的 file() 函数将整个文件读取到一个数组中。 此解决方案需要最少的输入量:

    $data = array_slice(file('file.txt'), -5);
    
    foreach ($data as $line) {
        echo $line;
    }
    
        9
  •  3
  •   OnoSendai    10 年前

    打开大文件 file() 可以生成一个大数组,保留相当大的内存块。

    您可以使用 SplFileObject 因为它遍历每一行。

    使用 seek 方法(方法) seekableiterator )取最后一行。然后您应该将当前键值减去5。

    要获取最后一行,请使用 PHP_INT_MAX . (是的,这是一个解决方法。)

    $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));
    
        10
  •  2
  •   over_optimistic    9 年前

    此功能适用于4GB以下的大型文件。这种速度来自于读取一大块数据,而不是一次读取一个字节并计算行数。

    // 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;
    }
    

    在运行上述函数之后,您可以在一个循环中执行fgets(),一次从$fh读取每一行。

        11
  •  1
  •   jasir    13 年前

    您可以使用我的小助手库(2个函数)

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

    然后只需使用:

    //read last 5 lines
    $lines = \jasir\FileHelpers\FileHelpers::readLastLines($pathToFile, 5);
    
        12
  •  0
  •   Kaibo    15 年前

    我已经测试过这个了。它对我有用。

    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);
    
    
    ?>
    
        13
  •  0
  •   Mikeys4u    12 年前

    RAM数量最少,输出良好。我同意保罗·狄克逊的观点…

    $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];
    }
    
        14
  •  0
  •   Muharrem Armağan Onur Kızıldağ    10 年前
    $dosya = "../dosya.txt";
    $array = explode("\n", file_get_contents($dosya));
    $reversed = array_reverse($array);
    for($x = 0; $x < 6; $x++) 
    {
        echo $reversed[$x];
    }
    
        15
  •  0
  •   Black    8 年前

    这是我的解决方案:

    /**
     *
     * 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;
    }
    
        16
  •  -2
  •   Mat    14 年前

    如果你的行间用CR隔开,或者你想试试 exploding 您的$data变量:

    $lines = explode("\n", $data);
    

    $lines最终应该是一个数组,您可以使用sizeof()计算出记录的数量,然后只得到最后5个。

        17
  •  -2
  •   TylerDurden    12 年前

    这是从文本文件中读取的最后10行

    $data = array_slice(file('logs.txt'),10);
    
        foreach ($data as $line) 
    
        {
    
            echo $line."<br/>";
        }
    
    推荐文章