PHP - 如何返回文件的最后一行?

21

我猜测是fgets函数,但我找不到具体的语法。我正在尝试读取日志文件中最后添加的行(我认为用字符串更容易)。

10个回答

50

最简单的朴素解决方案是:

$file = "/path/to/file";
$data = file($file);
$line = $data[count($data)-1];

虽然这样会将整个文件加载到内存中,可能会有问题(也可能没有)。更好的解决方法是:

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

1
请注意,除非您使用escapeshellarg(),否则这非常不安全:http://de.php.net/manual/en/function.escapeshellarg.php - soulmerge
8
更正一下,只有当你将用户输入作为文件路径时才会存在风险(这种情况下应该进行各种验证)。如果你像例子中使用常量的话,那么就没有问题了,而且你可以省去一个函数调用。当然,养成好习惯总是不错的选择。 - Matthew Scharley
1
进一步扩展您的评论(“如果您使用的是Mac...?”) - 如果您使用的是Windows并且没有“tail”命令怎么办?(我知道有一个“tail”的Win32端口,但这不是重点)。除此之外 - 即使我没有测试过,我也不会感到惊讶,每个请求都创建一个新进程可能不太可扩展。 - Tomalak
1
如果你在escapeshellarg的示例中包含其余所需的代码,那就太好了。创建一个字符串并不能神奇地获取任何内容的最后一行。 - carter
1
@carter 不是的;这是通过调用tail命令实现的。很遗憾,生活中没有什么是魔法。 - Matthew Scharley
显示剩余6条评论

15

看起来这就是你想要的:

tekkie.flashbit.net: PHP中的Tail功能

它实现了一个函数,使用负索引的fseek()从文件末尾向上滚动文件。您可以定义要返回多少行。

代码也可在GitHub的Gist中获得:

// full path to text file
define("TEXT_FILE", "/home/www/default-error.log");
// number of lines to read from the end of file
define("LINES_COUNT", 10);


function read_file($file, $lines) {
    //global $fsize;
    $handle = fopen($file, "r");
    $linecounter = $lines;
    $pos = -2;
    $beginning = false;
    $text = array();
    while ($linecounter > 0) {
        $t = " ";
        while ($t != "\n") {
            if(fseek($handle, $pos, SEEK_END) == -1) {
                $beginning = true; 
                break; 
            }
            $t = fgetc($handle);
            $pos --;
        }
        $linecounter --;
        if ($beginning) {
            rewind($handle);
        }
        $text[$lines-$linecounter-1] = fgets($handle);
        if ($beginning) break;
    }
    fclose ($handle);
    return array_reverse($text);
}

$fsize = round(filesize(TEXT_FILE)/1024/1024,2);

echo "<strong>".TEXT_FILE."</strong>\n\n";
echo "File size is {$fsize} megabytes\n\n";
echo "Last ".LINES_COUNT." lines of the file:\n\n";

$lines = read_file(TEXT_FILE, LINES_COUNT);
foreach ($lines as $line) {
    echo $line;
}

11
"utterly useless" 这个说法有点过于严厉。 - Tomalak
好的,也许有点吧...但是如果没有人能够证明使用tail确实导致瓶颈,而不是对大型数据库进行操作,我仍然不愿意使用它。 - Matthew Scharley
2
好吧,现在你要让我去基准测试它们了...因为我很好奇... - Matthew Scharley
1
我也是。所以,如果你对它进行基准测试,我会对你的发现感兴趣。:-) 理想情况下,这应该在一个太大而无法适应文件系统缓存的日志文件上进行测试。 - Tomalak
1
如果不能依赖于shell中的tail命令,例如非UNIX或某些共享主机,则以下代码非常有用;-) 由于php代码有效地使用fseek仅读取文件尾部,因此性能差异必须很小。 - PaulH
显示剩余2条评论

10
define('YOUR_EOL', "\n");
$fp = fopen('yourfile.txt', 'r');

$pos = -1; $line = ''; $c = '';
do {
    $line = $c . $line;
    fseek($fp, $pos--, SEEK_END);
    $c = fgetc($fp);
} while ($c != YOUR_EOL);

echo $line;

fclose($fp);

这样做更好,因为它不会将整个文件加载到内存中...

如果您使用与脚本所在操作系统的默认行尾相同的行尾,则将YOUR_EOL设置为正确的行尾。您可以使用常量PHP_EOL。


5
function seekLastLine($f) {
    $pos = -2;
    do {
        fseek($f, $pos--, SEEK_END);
        $ch = fgetc($f);
    } while ($ch != "\n");
}

-2的原因是最后一个字符可能是\n


2

要获取最后一行,你可以逐行读取文件并保存最后读取的那一行。

如果你在使用Unix/Linux系统,也可以考虑使用shell命令tail。

tail -n 1 filename

1
如果你想逐行读取文件,file 函数可以逐行读取文件内容,并将每一行作为数组的一个元素返回。
因此,你可以做一些简单的事情,比如:
$lines    = file('log.txt');
$lastLine = array_pop($lines);

3
真的吗?对于一个几兆字节的日志文件,其中99.99%对我来说都没有任何意义,我会尽量避免将其全部加载到数组中,然后立即将其丢弃。 - Tomalak
毫无疑问,这种方法效率低下,但它能够工作;而且谁知道文件有多长呢?在我的环境中,我会使用tail命令,但WiseDonkey没有指定任何命令。不过,你提供的那个函数很不错。 - Kieran Hall
1
file()和file_get_contents()都是非常好的文件操作函数,特别是当你知道所涉及的文件相对较小,只想快速轻松地完成一些操作时。 - Matthew Scharley
@Matthew Scharley:OP提到了日志文件。大多数情况下,这些日志文件与“相对较小”完全相反。 - Tomalak

1

为什么只读最后一行?

function readLines($fp, $num) {

    $line_count = 0; $line = ''; $pos = -1; $lines = array(); $c = '';

    while($line_count < $num) {
        $line = $c . $line; 
        fseek($fp, $pos--, SEEK_END);
        $c = fgetc($fp);
        if($c == "\n") { $line_count++; $lines[] = $line; $line = ''; $c = ''; }
    }   
    return $lines;
}

$filename = "content.txt";
$fp = @fopen($filename, "r");

print_r(readLines($fp, 2));

fclose($fp);

1

@unique_stephen,你的答案有缺陷。PHP fseek 成功返回 0,失败返回 -1。将结果存储在 $begining 中,然后稍后在 ftell() 的过滤器中使用它是不正确的。如果我的声望更高,我会投票否决并留下评论。这是 unique_stephen 函数的修改版本。

function readlastline($fileName)
{
    $fp = @fopen($fileName, "r");
    if (fseek($fp, 0) == -1)
        exit('Cannot seek to beginning of the file'); 
    $pos = -1;
    $t = " ";
    while ($t != "\n") {
        if (fseek($fp, $pos, SEEK_END) == -1)
            exit('Cannot seek to the end of the file');
        if (ftell($fp) == 0) {
            break;
        }
        $t = fgetc($fp);
        $pos = $pos - 1;
    }
    $t = fgets($fp);
    fclose($fp);
    return $t;
}

注意:PHP的fseek无法管理大于PHP_MAX_INT(32位有符号数字,即使在64位二进制文件上也是如此)的文件末尾。


1
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Aken Roberts
是的,已经有答案了。如果您不希望我把答案作为评论留下,那么无法在答案上发表评论的问题就需要解决了。我将编辑答案,以给出 @unique_stephen 的回复的正确版本。 - Earnie

1
function readlastline($fileName)
{

       $fp = @fopen($fileName, "r");
       $begining = fseek($fp, 0);      
       $pos = -1;
       $t = " ";
       while ($t != "\n") {
             fseek($fp, $pos, SEEK_END);
             if(ftell($fp) == $begining){
              break;
             }
             $t = fgetc($fp);
             $pos = $pos - 1;
       }
       $t = fgets($fp);
       fclose($fp);
       return $t;
}

欢迎来到SO!请不要发布仅包含代码的答案,而是加入一些文字说明您的方法如何工作以及它与其他给出答案的区别在哪里。您可以在我们的"如何撰写优质答案"页面了解更多信息。 - ahuemmer

1
这个程序不会在只有1或0行的文件中崩溃。
function readlastline($fileName)
{

       $fp = @fopen($fileName, "r");
       $begining = fseek($fp, 0);      
       $pos = -1;
       $t = " ";
       while ($t != "\n") {
             fseek($fp, $pos, SEEK_END);
             if(ftell($fp) == $begining){
              break;
             }
             $t = fgetc($fp);
             $pos = $pos - 1;
       }
       $t = fgets($fp);
       fclose($fp);
       return $t;
}

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