【问题标题】:How to read only 5 last line of the text file in PHP?如何在 PHP 中只读取文本文件的最后 5 行?
【发布时间】:2011-02-27 01:04:20
【问题描述】:

我有一个名为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

【问题讨论】:

标签: php fopen


【解决方案1】:

如果您的行由 CR 或 LF 分隔,您可以尝试 exploding 您的 $data 变量:

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

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

【讨论】:

  • 这会消耗大量的内存来形成一个巨大的数组,对于大文件的情况。
  • 此外,这种方法不会成功地分隔所有行尾。至少应该使用preg_split('/\n|\r\n?/', $data)。但是话又说回来,这不是解决 OP 问题的正确方法。
【解决方案2】:

未经测试的代码,但应该可以工作:

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

调用 max 将处理少于 6 行的文件。

【讨论】:

  • 如果 filename.txt 只包含 3 行怎么办?
  • 这很好,但需要注意一个关键问题。也就是说 $i 可能小于 0;所以你需要这个 if ($i > 0) echo ....
  • 不是很好,因为如果日志文件很大,这会消耗大量 RAM。
  • 内存使用方面的糟糕代码。它将整个文件放入内存中。如果该文件将超级大,那么您就有麻烦了。
【解决方案3】:
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;
}

这对于较大的文件会很快,但对于一个简单的任务有很多代码,如果有 LARGE FILES,请使用它

ReadFromEndByLine('myFile.txt',6);

【讨论】:

  • 这需要将整个文件读入内存,这可能很糟糕。
  • 更新了更大的代码块,但速度更快,内存使用量更少。 - 取自mydebian.blogdns.org/?p=197
  • while ($lines &gt; 0) { 给出未定义变量通知
【解决方案4】:

对于一个大文件,使用 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);

【讨论】:

  • @Paual Dixon,在 while 循环中,您读取所有行并存储最后 X 行。是否可以读取最后 X 行?
【解决方案5】:

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

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

foreach ($data as $line) {
    echo $line;
}

【讨论】:

  • 这是一个很棒的解决方案!谢谢!
【解决方案6】:

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

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

否则,您将不得不数行并取最后 5 行,例如:

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

【讨论】:

  • 文件数组切片不是处理大内存文件的好方法。 tail 是我的方向。
【解决方案7】:

这是一个常见的面试问题。这是我去年被问到这个问题时写的。请记住,您在 Stack Overflow 上获得的代码是通过 Creative Commons Share-Alikeattribution 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). 
 * Attribution: https://stackoverflow.com/a/2961731/3389585
 */

$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() 会进一步递增返回(每次加倍) ) 直到我们得到足够的行数。

【讨论】:

  • $data 已设置但从未使用过。你确定这个 sn-p 会 echo 任何匹配的行吗?
  • @mjohns 那是 10 年前的事了,但我记得我确实测试过它。在 PHP 中 fseek() 的返回值只是 0 表示成功或 -1 表示失败。我同意检查此状态是合适的。
【解决方案8】:

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

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


?>

【讨论】:

    【解决方案9】:

    这不使用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
    ?>
    

    【讨论】:

      【解决方案10】:

      此功能适用于 4GB 以下的非常大的文件。速度来自读取大量数据而不是一次读取 1 个字节并计算行数。

      // 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】:

        你可以使用我的小助手库(2个功能)

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

        然后只需使用:

        //read last 5 lines
        $lines = \jasir\FileHelpers\FileHelpers::readLastLines($pathToFile, 5);
        

        【讨论】:

          【解决方案12】:

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

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

          【讨论】:

            【解决方案13】:

            内存最少,输出良好。我同意保罗·迪克森的观点……

            $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】:
              $dosya = "../dosya.txt";
              $array = explode("\n", file_get_contents($dosya));
              $reversed = array_reverse($array);
              for($x = 0; $x < 6; $x++) 
              {
                  echo $reversed[$x];
              }
              

              【讨论】:

                【解决方案15】:

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

                【讨论】:

                • 我推荐这个作为大文件的快速方法
                【解决方案16】:

                这里的大多数选项假设将文件读入内存然后处理行。如果文件太大,这不是一个好主意

                我认为最好的方法是使用一些操作系统实用程序,例如 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 ...
                

                【讨论】:

                • $output 是一个数组,所以如果你在回显,那么你需要使用 implode()
                【解决方案17】:

                这是我的解决方案:

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

                【讨论】:

                  【解决方案18】:

                  快速

                  这是具有低内存成本的大型文件的 FAST 方法 - 我通过将他的代码包装在方便的函数中并添加反向功能来开发 Wallace Maxters answer(如果你想投票 - 按照他的答案去做)

                  function readLastLines($filename, $num, $reverse = false)
                  {
                      $file = new \SplFileObject($filename, 'r');
                      $file->seek(PHP_INT_MAX);
                      $last_line = $file->key();
                      $lines = new \LimitIterator($file, $last_line - $num, $last_line);
                      $arr = iterator_to_array($lines);
                      if($reverse) $arr = array_reverse($arr);
                      return implode('',$arr);
                  }
                  
                  // use it by
                  $lines = readLastLines("file.txt", 5) // return string with 5 last lines
                  

                  【讨论】:

                  • 简洁明了,没有不必要的类和类方法——谢谢。
                  【解决方案19】:

                  虽然我喜欢 kamilwallace 的优雅答案,但我添加了一个额外的行来处理没有 $num 中定义的行数的文件。

                  function readLastLines($filename, $num, $reverse = false) {
                      $file = new \SplFileObject($filename, 'r');
                      $file->seek(PHP_INT_MAX);
                      $last_line = $file->key();
                      $first_line = $last_line - $num < 0 ? 0 : $last_line - $num;
                      $lines = new \LimitIterator($file, $first_line, $last_line);
                      $arr = iterator_to_array($lines);
                      if($reverse) $arr = array_reverse($arr);
                      return implode('', $arr);
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2010-10-04
                    • 2012-05-11
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-09-23
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-06-06
                    相关资源
                    最近更新 更多