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

Ali*_*eza 30 php fopen

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

我正在通过这段代码阅读它:

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

并出现了大量的线条.我只是想回显文件的最后5行

我怎样才能做到这一点 ?


file.txt是这样的:

11111111111111
22222222222

33333333333333
44444444444

55555555555555
66666666666
Run Code Online (Sandbox Code Playgroud)

Pau*_*xon 44

对于大文件,使用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);
Run Code Online (Sandbox Code Playgroud)

您可以通过寻找一个位置(例如,从末尾开始大约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);
Run Code Online (Sandbox Code Playgroud)


Mae*_*lyn 20

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

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

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

  • 不太好,因为在日志文件很大的情况下,这会消耗大量的RAM. (7认同)
  • 如果filename.txt只包含3行怎么办? (4认同)
  • 就内存使用而言,代码很糟糕。它将整个文件放入内存中。如果该文件非常大,那么您就有麻烦了。 (2认同)

Rob*_*itt 14

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;
}
Run Code Online (Sandbox Code Playgroud)

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

ReadFromEndByLine( 'MYFILE.TXT',6);


Rob*_*Rob 13

如果你在Linux系统上,你可以这样做:

$lines = `tail -5 /path/to/file.txt`;
Run Code Online (Sandbox Code Playgroud)

否则你将不得不计算线数并取最后5个,例如:

$all_lines = file('file.txt');
$last_5 = array_slice($all_lines , -5);
Run Code Online (Sandbox Code Playgroud)


Bil*_*win 10

这是一个常见的面试问题.这是我去年被问到这个问题时所写的内容.请记住,您在Stack Overflow上获得的代码已获得Creative Commons Share-Alike的许可,且需要归属.

<?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];
  }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案确实假设最大线长度.面试官问我如果不能做出这个假设我将如何解决问题,并且必须容纳可能比我选择的任何最大长度更长的线.

我告诉他,任何软件项目都必须做出某些假设,但我可以测试是否$c小于所需的行数,如果不是,则fseek()进一步递增(每次加倍)直到我们得到足够的行.


Wal*_*ers 7

使用打开大文件file()可以生成一个大数组,从而保留大量内存。

您可以SplFileObject通过迭代每一行来减少内存成本。

使用seek(of 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));
Run Code Online (Sandbox Code Playgroud)


Kam*_*ski 7

快速地

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

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
Run Code Online (Sandbox Code Playgroud)


duk*_*vin 6

这不使用,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
?>
Run Code Online (Sandbox Code Playgroud)


小智 6

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

我认为最好的方法是使用某些OS实用程序,例如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 ...
Run Code Online (Sandbox Code Playgroud)