从文件中读取最后一行

37

我遇到了一个问题。我在一个Linux系统上有一个日志文件,其中记录了几个正在运行的进程的输出信息。这个文件有时可能会变得非常大,而我需要读取该文件的最后一行。

问题在于,通过AJAX请求频繁调用此操作,当该日志文件大小超过5-6MB时,对服务器来说就不太好。因此,我的想法是读取最后一行,但不要读取整个文件并通过它或将其加载到内存中,因为那样会使我的电脑开销过大。

是否有任何优化操作,以使其运行平稳,不会损害服务器或杀死Apache?

我还有另一个选择,即使用exec('tail -n 1 /path/to/log'),但这听起来不太好。

后来编辑:我不想将文件放入内存,因为它可能变得非常大。fopen()不是一个选项。


https://dev59.com/A2Up5IYBdhLWcg3wz6AH - zloctb
13个回答

57
这应该可以运行:
$line = '';

$f = fopen('data.txt', 'r');
$cursor = -1;

fseek($f, $cursor, SEEK_END);
$char = fgetc($f);

/**
 * Trim trailing newline chars of the file
 */
while ($char === "\n" || $char === "\r") {
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

/**
 * Read until the start of file or first newline char
 */
while ($char !== false && $char !== "\n" && $char !== "\r") {
    /**
     * Prepend the new char
     */
    $line = $char . $line;
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

fclose($f);

echo $line;
请注意,此解决方案会重复该行的最后一个字符,除非您的文件以换行符结束。如果您的文件没有以换行符结尾,则可以将两个$cursor--实例更改为--$cursor

请注意,此解决方案会重复该行的最后一个字符,除非您的文件以换行符结束。如果您的文件没有以换行符结尾,则可以将两个$cursor--实例更改为--$cursor


8
你的意思是你不想阅读这个文件?我并不会将整个文件读入内存中,而只是打开一个指向它的指针,然后逐个字符地搜索。这是处理大型文件最有效的方法。 - Ionuț G. Stan
22
fopen()file_get_contents() 的行为不同。 - Ionuț G. Stan
1
fopen不会将文件加载到内存中,它只是创建一个文件描述符(指针)。 - Wadih M.
3
当我的最后一行是 </rss> 时,我发现最后一个字符被重复了。这段代码把 </rss>> 放进了 $line 里面。为了修正这个问题,我把两个 $cursor-- 的实例改成了 --$cursor - rg89
1
我确认rg89的观察结果。如果文件没有以换行符结尾,那么代码将会输出最后一个字符两次。例如1:123\n456 - 输出:4566(不正确)。例如2:123\n456\n - 输出:456(按预期工作)。顺便说一句,这是一个很好的解决方案。 - Kristoffer Bohmann
显示剩余4条评论

19

使用 fseek 函数。将文件指针定位到最后的位置,并向后倒退查找(使用 ftell 函数获取当前位置),直到找到一个 "\n" 字符。


$fp = fopen(".....");
fseek($fp, -1, SEEK_END); 
$pos = ftell($fp);
$LastLine = "";
// Loop backword util "\n" is found.
while((($C = fgetc($fp)) != "\n") && ($pos > 0)) {
    $LastLine = $C.$LastLine;
    fseek($fp, $pos--);
}
fclose($fp);

注意:我没有测试过。 你可能需要做一些调整。

更新:感谢 Syntax Error 指出空文件的问题。

:-D

更新2:修复了另一个语法错误,在 $LastLine = "" 缺少分号。


1
如果文件为空,则会出现无限循环。使用以下代码进行修复:while(($C = fgetc($fp)) != "\n" && $pos > 0 ) { - Syntax Error
@SyntaxError 这个问题已经在编辑后的版本中解决了,很好知道! - Basj

6
您需要寻找fseek函数。那里的评论区有如何读取文件的最后一行的可运行示例。

6
这是 Ionuț G. Stan 的代码:

我稍微修改了您的代码,将其转化为可重用的函数。

function read_last_line ($file_path){



$line = '';

$f = fopen($file_path, 'r');
$cursor = -1;

fseek($f, $cursor, SEEK_END);
$char = fgetc($f);

/**
* Trim trailing newline chars of the file
*/
while ($char === "\n" || $char === "\r") {
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

/**
* Read until the start of file or first newline char
*/
while ($char !== false && $char !== "\n" && $char !== "\r") {
    /**
     * Prepend the new char
     */
    $line = $char . $line;
    fseek($f, $cursor--, SEEK_END);
    $char = fgetc($f);
}

return $line;
}

echo read_last_line('log.txt');

你会获得最后一行。


注:该代码是用于读取文件中最后一行的。

4
function readlastline() 
{ 
       $fp = @fopen("/dosmnt/LOGFILE.DAT", "r"); 
       $pos = -1; 
       $t = " "; 
       while ($t != "\n") { 
             fseek($fp, $pos, SEEK_END); 
             $t = fgetc($fp); 
             $pos = $pos - 1; 
       } 
       $t = fgets($fp); 
       fclose($fp); 
       return $t; 
} 

来源: http://forums.devshed.com/php-development-5/php-quick-way-to-read-last-line-156010.html

这是一个关于PHP技术的帖子,它讨论了一种快速读取文件最后一行的方法。这个方法使用了fseek和fgets函数,它们都是针对文件操作的基本函数。通过计算文件大小和读取最后一行的长度,就能很容易地获取文件的最后一行内容。这个方法比其他一些方法更加简洁和高效,特别是当处理大型文件时。

如果文件正在不断地被写入,您可能希望在开始时存储文件的当前长度,然后从该位置向后寻找。这样,即使在您寻找时添加了更多行,您也可以保证找到完整的一行。如果性能是一个考虑因素,您可以抓取块而不是逐字节寻找。 - David Harkness
怎么样用do...while循环,这样你就不需要初始化$t=" "; 这样会显得有点不够规范。;) - OCDev

4
如果你知道行长的上限,你可以像这样操作。
$maxLength = 1024;
$fp = fopen('somefile.txt', 'r');
fseek($fp, -$maxLength , SEEK_END); 
$fewLines = explode("\n", fgets($fp, $maxLength));
$lastLine = $fewLines[count($fewLines) - 1];

回复编辑:fopen仅获取文件句柄(即确保文件存在、进程具有权限、让操作系统知道进程正在使用该文件等...)。在本例中,只有来自文件的1024个字符将被读入内存。


如果最后一行有1025个字符怎么办? - Sebastian Mach
1
这似乎是针对你知道有短行的文件的简短解决方案。它不适用于具有大行的大型文件。 - bksi

2

您的问题看起来与这个类似。

避免将整个文件加载到内存中的最佳方法似乎是:

$file = escapeshellarg($file); // for the security concious (should be everyone!)
$line = `tail -n 1 $file`;

不,事情并不那么简单。我不能每次都将文件加载到RAM中。它可能很容易就会达到10-20MB,而且每个客户端每秒钟都会发出请求。这不是一个解决方案,因为Apache会崩溃。 - Bogdan Constantinescu
“tail -n 1 /path/to/log”选项写在问题正文中,不太友好。 - Bogdan Constantinescu
如果您喜欢,还有一个稍微简短的选项: sed -n /path/to/log - James Goodwin

0

来自http://php.net/manual/zh/function.fseek.php评论区的未经测试代码

jim at lfchosting dot com 05-Nov-2003 02:03
Here is a function that returns the last line of a file.  This should be quicker than reading the whole file till you get to the last line.  If you want to speed it up a bit, you can set the $pos = some number that is just greater than the line length.  The files I was dealing with were various lengths, so this worked for me. 

<?php 
function readlastline($file) 
{ 
        $fp = @fopen($file, "r"); 
        $pos = -1; 
        $t = " "; 
        while ($t != "\n") { 
              fseek($fp, $pos, SEEK_END); 
              $t = fgetc($fp); 
              $pos = $pos - 1; 
        } 
        $t = fgets($fp); 
        fclose($fp); 
        return $t; 
} 
?>

0

这是我的解决方案,只使用了一个循环

        $line = '';
        $f = fopen($file_path, 'r');
        $cursor = 0 ;
        do  {
            fseek($f, $cursor--, SEEK_END);
            $char = fgetc($f);
            $line = $char.$line;
        } while (
                $cursor > -1 || (
                 ord($char) !== 10 &&
                 ord($char) !== 13
                )
        );

0
这是这里答案的编译,包装成一个功能,可以指定要返回多少行。
function getLastLines($path, $totalLines) {
  $lines = array();

  $fp = fopen($path, 'r');
  fseek($fp, -1, SEEK_END);
  $pos = ftell($fp);
  $lastLine = "";

  // Loop backword until we have our lines or we reach the start
  while($pos > 0 && count($lines) < $totalLines) {

    $C = fgetc($fp);
    if($C == "\n") {
      // skip empty lines
      if(trim($lastLine) != "") {
        $lines[] = $lastLine;
      }
      $lastLine = '';
    } else {
      $lastLine = $C.$lastLine;
    }
    fseek($fp, $pos--);
  }

  $lines = array_reverse($lines);

  return $lines;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接