我可以从我的一端读取 PHP 文件,例如,如果我想读取最后 10-20 行吗?
而且,正如我所读到的,如果文件大小超过 10mbs,我就会开始收到错误。
如何防止此错误?
为了读取普通文件,我们使用代码:
if ($handle) {
while (($buffer = fgets($handle, 4096)) !== false) {
$i1++;
$content[$i1]=$buffer;
}
if (!feof($handle)) {
echo "Error: unexpected fgets() fail\n";
}
fclose($handle);
}
我的文件可能超过 10mbs,但我只需要阅读最后几行。我该怎么做?
谢谢
您可以使用 fopen 和 fseek 在文件中从末尾向后导航。例如
$fp = @fopen($file, "r");
$pos = -2;
while (fgetc($fp) != "\n") {
fseek($fp, $pos, SEEK_END);
$pos = $pos - 1;
}
$lastline = fgets($fp);
这不是纯 PHP,但常见的解决方案是使用 tac 命令,该命令是
cat
的恢复并反向加载文件。使用 exec() 或 passthru() 在服务器上运行它,然后读取结果。用法示例:
<?php
$myfile = 'myfile.txt';
$command = "tac $myfile > /tmp/myfilereversed.txt";
exec($command);
$currentRow = 0;
$numRows = 20; // stops after this number of rows
$handle = fopen("/tmp/myfilereversed.txt", "r");
while (!feof($handle) && $currentRow <= $numRows) {
$currentRow++;
$buffer = fgets($handle, 4096);
echo $buffer."<br>";
}
fclose($handle);
?>
这取决于你如何解释“可以”。
如果您想知道是否可以在不阅读前面所有行的情况下直接(使用 PHP 函数)执行此操作,那么答案是:不,您不能。
行尾是对数据的解释,只有实际读取数据才能知道它们在哪里。
如果它是一个非常大的文件,我不会这样做。 如果从尾部开始扫描文件,从尾部逐渐读取到文件中的块会更好。
更新
这是一种仅限 PHP 的方法,可以读取文件的最后 n 行,而无需通读所有内容:
function last_lines($path, $line_count, $block_size = 512){
$lines = array();
// we will always have a fragment of a non-complete line
// keep this in here till we have our next entire line.
$leftover = "";
$fh = fopen($path, 'r');
// go to the end of the file
fseek($fh, 0, SEEK_END);
do{
// need to know whether we can actually go back
// $block_size bytes
$can_read = $block_size;
if(ftell($fh) < $block_size){
$can_read = ftell($fh);
}
// go back as many bytes as we can
// read them to $data and then move the file pointer
// back to where we were.
fseek($fh, -$can_read, SEEK_CUR);
$data = fread($fh, $can_read);
$data .= $leftover;
fseek($fh, -$can_read, SEEK_CUR);
// split lines by \n. Then reverse them,
// now the last line is most likely not a complete
// line which is why we do not directly add it, but
// append it to the data read the next time.
$split_data = array_reverse(explode("\n", $data));
$new_lines = array_slice($split_data, 0, -1);
$lines = array_merge($lines, $new_lines);
$leftover = $split_data[count($split_data) - 1];
}
while(count($lines) < $line_count && ftell($fh) != 0);
if(ftell($fh) == 0){
$lines[] = $leftover;
}
fclose($fh);
// Usually, we will read too many lines, correct that here.
return array_slice($lines, 0, $line_count);
}
以下片段对我有用。
$file = popen("tac $filename",'r');
while ($line = fgets($file)) {
echo $line;
}
参考:http://laughingmeme.org/2008/02/28/reading-a-file-backwards-in-php/
如果您的代码无法正常工作并报告错误,您应该在帖子中包含该错误!
您收到错误的原因是因为您试图将文件的全部内容存储在 PHP 的内存空间中。
解决问题的最有效方法是按照 Greenisha 的建议,找到文件末尾,然后返回一点。但格林尼沙的后退机制并不是很有效。
考虑从流中获取最后几行的方法(即您无法查找的位置):
while (($buffer = fgets($handle, 4096)) !== false) {
$i1++;
$content[$i1]=$buffer;
unset($content[$i1-$lines_to_keep]);
}
因此,如果您知道最大行长度是 4096,那么您会:
if (4096*lines_to_keep<filesize($input_file)) {
fseek($fp, -4096*$lines_to_keep, SEEK_END);
}
然后应用我之前描述的循环。
由于 C 有一些更有效的方法来处理字节流,因此最快的解决方案(在 POSIX/Unix/Linux/BSD 上)系统将很简单:
$last_lines=system("last -" . $lines_to_keep . " filename");
对于Linux你可以做
$linesToRead = 10;
exec("tail -n{$linesToRead} {$myFileName}" , $content);
您将在 $content 变量中获得一个行数组
纯PHP解决方案
$f = fopen($myFileName, 'r');
$maxLineLength = 1000; // Real maximum length of your records
$linesToRead = 10;
fseek($f, -$maxLineLength*$linesToRead, SEEK_END); // Moves cursor back from the end of file
$res = array();
while (($buffer = fgets($f, $maxLineLength)) !== false) {
$res[] = $buffer;
}
$content = array_slice($res, -$linesToRead);
如果你知道这些行有多长,你就可以避免很多黑魔法,只需抓住文件末尾的一块即可。
我需要一个非常大的日志文件中的最后 15 行,总共大约有 3000 个字符。所以为了安全起见,我只是抓取最后 8000 个字节,然后正常读取文件并从末尾获取我需要的内容。
$fh = fopen($file, "r");
fseek($fh, -8192, SEEK_END);
$lines = array();
while($lines[] = fgets($fh)) {}
这可能比评分最高的答案更有效,后者逐个字符读取文件,比较每个字符,并根据换行符进行分割。
这是另一种解决方案。 fgets() 中没有行长度控制,您可以添加它。
/* Read file from end line by line */
$fp = fopen( dirname(__FILE__) . '\\some_file.txt', 'r');
$lines_read = 0;
$lines_to_read = 1000;
fseek($fp, 0, SEEK_END); //goto EOF
$eol_size = 2; // for windows is 2, rest is 1
$eol_char = "\r\n"; // mac=\r, unix=\n
while ($lines_read < $lines_to_read) {
if (ftell($fp)==0) break; //break on BOF (beginning...)
do {
fseek($fp, -1, SEEK_CUR); //seek 1 by 1 char from EOF
$eol = fgetc($fp) . fgetc($fp); //search for EOL (remove 1 fgetc if needed)
fseek($fp, -$eol_size, SEEK_CUR); //go back for EOL
} while ($eol != $eol_char && ftell($fp)>0 ); //check EOL and BOF
$position = ftell($fp); //save current position
if ($position != 0) fseek($fp, $eol_size, SEEK_CUR); //move for EOL
echo fgets($fp); //read LINE or do whatever is needed
fseek($fp, $position, SEEK_SET); //set current position
$lines_read++;
}
fclose($fp);
在寻找相同的东西时,我可以浏览以下内容,并认为它对其他人也可能有用,因此在这里分享:
/* 从末尾开始逐行读取文件 */
function tail_custom($filepath, $lines = 1, $adaptive = true) {
// Open file
$f = @fopen($filepath, "rb");
if ($f === false) return false;
// Sets buffer size, according to the number of lines to retrieve.
// This gives a performance boost when reading a few lines from the file.
if (!$adaptive) $buffer = 4096;
else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
// Jump to last character
fseek($f, -1, SEEK_END);
// Read it and adjust line number if necessary
// (Otherwise the result would be wrong if file doesn't end with a blank line)
if (fread($f, 1) != "\n") $lines -= 1;
// Start reading
$output = '';
$chunk = '';
// While we would like more
while (ftell($f) > 0 && $lines >= 0) {
// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$output = ($chunk = fread($f, $seek)) . $output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, "\n");
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {
// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);
}
// Close file and return
fclose($f);
return trim($output);
}
正如爱因斯坦所说,一切事情都应该变得尽可能简单,但不能更简单。此时您需要一个数据结构、LIFO 数据结构或简单地放置一个堆栈。
此处提供了上述“尾部”建议的更完整示例。这似乎是一个简单而有效的方法——谢谢。非常大的文件应该不是问题,并且不需要临时文件。
$out = array();
$ret = null;
// capture the last 30 files of the log file into a buffer
exec('tail -30 ' . $weatherLog, $buf, $ret);
if ( $ret == 0 ) {
// process the captured lines one at a time
foreach ($buf as $line) {
$n = sscanf($line, "%s temperature %f", $dt, $t);
if ( $n > 0 ) $temperature = $t;
$n = sscanf($line, "%s humidity %f", $dt, $h);
if ( $n > 0 ) $humidity = $h;
}
printf("<tr><th>Temperature</th><td>%0.1f</td></tr>\n",
$temperature);
printf("<tr><th>Humidity</th><td>%0.1f</td></tr>\n", $humidity);
}
else { # something bad happened }
在上面的示例中,代码读取 30 行文本输出并显示文件中最后的温度和湿度读数(这就是 printf 位于循环之外的原因,以防您想知道)。该文件由 ESP32 填充,即使传感器仅报告 nan,它也会每隔几分钟添加到文件中。所以三十行可以得到足够的阅读量,所以它永远不会失败。每个读数都包括日期和时间,因此在最终版本中,输出将包括读数的时间。