如何在 PHP 中只读取文本文件的最后 5 行?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/2961618/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to read only 5 last line of the text file in PHP?
提问by Alireza
I have a file named file.txtwhich is update by adding lines to it. 
我有一个名为的文件file.txt,它通过向其中添加行来更新。
I am reading it by this code:
我正在通过以下代码阅读它:
$fp = fopen("file.txt", "r");
$data = "";
while(!feof($fp))
{
$data .= fgets($fp, 4096);
}
echo $data;
and a huge number of lines appears. I just want to echo the last 5 lines of the file
并且出现了大量的行。我只想回显文件的最后 5 行
How can I do that ?
我怎样才能做到这一点 ?
The file.txtis like this:
该file.txt是这样的:
11111111111111
22222222222
33333333333333
44444444444
55555555555555
66666666666
采纳答案by Maerlyn
Untested code, but should work:
未经测试的代码,但应该可以工作:
$file = file("filename.txt");
for ($i = max(0, count($file)-6); $i < count($file); $i++) {
  echo $file[$i] . "\n";
}
Calling maxwill handle the file being less than 6 lines.
调用max将处理少于 6 行的文件。
回答by Paul Dixon
For a large file, reading all the lines into an array with file() is a bit wasteful. Here's how you could read the file and maintain a buffer of the last 5 lines:
对于大文件,用 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);
You could optimize this a bit more with some heuristics about likely line length by seeking to a position, say, approx 10 lines from the end, and going further back if that doesn't yield 5 lines. Here's a simple implementation which demonstrates that:
您可以通过寻找一个位置(例如,距离末端约 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);
回答by Rob
If you're on a linux system you could do this:
如果您使用的是 linux 系统,则可以执行以下操作:
$lines = `tail -5 /path/to/file.txt`;
Otherwise you'll have to count lines and take the last 5, something like:
否则,您将不得不计算行数并取最后 5 行,例如:
$all_lines = file('file.txt');
$last_5 = array_slice($all_lines , -5);
回答by RobertPitt
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;
}
This will be fast for larger files but alot of code for a simple task, if there LARGE FILES, use this
这对于较大的文件来说会很快,但是对于一个简单的任务需要很多代码,如果有大文件,请使用它
ReadFromEndByLine('myFile.txt',6);
ReadFromEndByLine('myFile.txt',6);
回答by Bill Karwin
This is a common interview question. Here's what I wrote last year when I was asked this question. Remember that code you get on Stack Overflow is licensed with the Creative Commons Share-Alikewith 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). 
 * 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];
  }
}
This solution does make an assumption about the maximum line length. The interviewer asked me how I would solve the problem if I couldn't make that assumption, and had to accommodate lines that were potentially longer than any max length I chose.
此解决方案确实对最大行长做出了假设。面试官问我,如果我不能做出这个假设,我将如何解决这个问题,并且不得不容纳可能比我选择的任何最大长度都长的行。
I told him that any software project has to make certain assumptions, but I could test if $cwas less than the desired number of lines, and if it isn't, fseek()back further incrementally (doubling each time) until we do get enough lines.
我告诉他,任何软件项目都必须做出某些假设,但我可以测试是否$c少于所需的行数,如果不是,则fseek()进一步递增(每次加倍),直到我们得到足够的行数。
回答by Anton
The most of options here suppose to read file into the memory and then work with rows. This wouldnt be a good idea, if the file too large
这里的大多数选项都假设将文件读入内存然后处理行。如果文件太大,这不是一个好主意
I think the best way is to use some OS-utility, like 'tail' in unix.
我认为最好的方法是使用一些操作系统实用程序,例如 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 ...
回答by Wallace Maxters
Opening large files with file()can generate a large array, reserving a considerable chunk of memory.
打开大文件file()可以生成一个大数组,保留相当大的内存块。
You can reduce the memory cost with SplFileObjectsince it iterates through each line.
您可以减少内存成本,SplFileObject因为它会遍历每一行。
Use the seekmethod (of seekableiterator) to fetch the last line. You should then subtract the current key value by 5.
使用seek方法 (of seekableiterator) 获取最后一行。然后,您应该将当前键值减去 5。
To obtain the last line, use PHP_INT_MAX. (Yes, this is a workaround.)
要获取最后一行,请使用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));
回答by lemon
This doesn't use file()so it will be more efficient for huge files;
这不会使用,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
?>
回答by over_optimistic
This function will work for REALLY large files under 4GB. The speed comes from reading a big chunk of data instead of 1 byte at a time and counting lines.
此功能适用于 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;
}
After running above function, you can just do fgets() in a loop to read each line at a time from $fh.
运行上述函数后,您可以在循环中执行 fgets() 以从 $fh 一次读取每一行。

