Ответ 1
Неподтвержденный код, но он должен работать:
$file = file("filename.txt");
for ($i = max(0, count($file)-6); $i < count($file); $i++) {
echo $file[$i] . "\n";
}
Вызов max
будет обрабатывать файл менее 6 строк.
У меня есть файл с именем file.txt
который обновляется путем добавления строк к нему.
Я читаю это по этому коду:
$fp = fopen("file.txt", "r");
$data = "";
while(!feof($fp))
{
$data .= fgets($fp, 4096);
}
echo $data;
и появляется огромное количество строк. Я просто хочу повторить последние 5 строк файла
Как я могу это сделать?
file.txt
выглядит так:
11111111111111
22222222222
33333333333333
44444444444
55555555555555
66666666666
Неподтвержденный код, но он должен работать:
$file = file("filename.txt");
for ($i = max(0, count($file)-6); $i < count($file); $i++) {
echo $file[$i] . "\n";
}
Вызов max
будет обрабатывать файл менее 6 строк.
Для большого файла чтение всех строк в массив с файлом() немного расточительно. Здесь вы можете прочитать файл и сохранить буфер из последних 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);
Вы можете оптимизировать это немного больше с некоторой эвристикой относительно вероятной длины линии, стремясь к позиции, скажем, приблизительно 10 строк от конца, и идите назад, если это не дает 5 строк. Вот простая реализация, которая демонстрирует, что:
//how many lines?
$linecount=5;
//what 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 seek even further back
}
fclose($fp);
var_dump($lines);
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 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;
}
Это будет быстро для больших файлов, но, кроме кода для простой задачи, если есть БОЛЬШИЕ ФАЙЛЫ, используйте этот
ReadFromEndByLine ( 'myfile.txt', 6);
Если вы используете систему Linux, вы можете сделать это:
$lines = `tail -5 /path/to/file.txt`;
В противном случае вам придется подсчитывать строки и принимать последние 5, что-то вроде:
$all_lines = file('file.txt');
$last_5 = array_slice($all_lines , -5);
Это распространенный вопрос интервью. Вот что я написал в прошлом году, когда мне задавали этот вопрос. Помните, что код, который вы получаете в 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];
}
}
Это решение делает предположение о максимальной длине линии. Интервьюер спросил меня, как бы я решил проблему, если бы я не мог сделать такое предположение, и мне пришлось учесть строки, которые были потенциально длиннее любой максимальной длины, которую я выбрал.
Я сказал ему, что любой программный проект должен делать определенные предположения, но я мог бы проверить, было ли $c
меньше, чем желаемое количество строк, а если нет, то fseek()
возвращался назад постепенно (удваивая каждый раз), пока мы не сделаем получить достаточно строк.
В большинстве случаев здесь предполагается прочитать файл в памяти, а затем работать со строками. Это не будет хорошей идеей, если файл слишком большой
Я думаю, что лучший способ - использовать некоторую ОС-утилиту, такую как "tail" в unix.
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 ...
Это не использует 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
?>
Открытие больших файлов с помощью file()
может генерировать большой массив, сохраняя значительную часть памяти.
Вы можете уменьшить стоимость памяти с помощью SplFileObject
, так как она выполняет итерацию по каждой строке.
Используйте метод seek
(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));
PHP file() функция считывает весь файл в массив. Это решение требует наименьшего количества ввода:
$data = array_slice(file('file.txt'), -5);
foreach ($data as $line) {
echo $line;
}
Эта функция будет работать для ДЕЙСТВИТЕЛЬНО больших файлов под 4 ГБ. Скорость происходит от чтения большого фрагмента данных вместо 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;
}
После запуска над функцией вы можете просто сделать fgets() в цикле, чтобы читать каждую строку за раз от $fh.
Вы можете использовать мою небольшую вспомогательную библиотеку (2 функции)
https://github.com/jasir/file-helpers
Затем просто используйте:
//read last 5 lines
$lines = \jasir\FileHelpers\FileHelpers::readLastLines($pathToFile, 5);
Вот БЫСТРЫЙ метод для БОЛЬШИХ файлов - я разрабатываю ответ Уоллеса Макстерса (если хотите повысить голосование - делайте это на его ответе), оборачивая его код в удобную функцию и добавляя обратную функцию
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
Я тестировал этот. Это работает для меня.
function getlast($filename,$linenum_to_read,$linelength){
// this function takes 3 arguments;
if (!$linelength){ $linelength = 600;}
$f = fopen($filename, 'r');
$linenum = filesize($filename)/$linelength;
for ($i=1; $i<=($linenum-$linenum_to_read);$i++) {
$data = fread($f,$linelength);
}
echo "<pre>";
for ($j=1; $j<=$linenum_to_read+1;$j++) {
echo fread($f,$linelength);
}
echo "</pre><hr />The filesize is:".filesize("$filename");
}
getlast("file.txt",6,230);
?>
Наименьшее количество бара и выходы хорошо. Я согласен с Полом Диксоном...
$lines=array();
$fp = fopen("userlog.txt", "r");
while(!feof($fp))
{
$line = fgets($fp, 4096);
array_push($lines, $line);
if (count($lines)>25)
array_shift($lines);
}
fclose($fp);
while ($a <= 10) {
$a++;
echo "<br>".$lines[$a];
}
$dosya = "../dosya.txt";
$array = explode("\n", file_get_contents($dosya));
$reversed = array_reverse($array);
for($x = 0; $x < 6; $x++)
{
echo $reversed[$x];
}
Вот мое решение:
/**
*
* Reads N lines from a file
*
* @param type $file path
* @param type $maxLines Count of lines to read
* @param type $reverse set to true if result should be reversed.
* @return string
*/
public function readLinesFromFile($file, $maxLines, $reverse=false)
{
$lines = file($file);
if ($reverse) {
$lines = array_reverse($lines);
}
$tmpArr = array();
if ($maxLines > count($lines))
exit("\$maxLines ist größer als die Anzahl der Zeilen in der Datei.");
for ($i=0; $i < $maxLines; $i++) {
array_push($tmpArr, $lines[$i]);
}
if ($reverse) {
$tmpArr = array_reverse($tmpArr);
}
$out = "";
for ($i=0; $i < $maxLines; $i++) {
$out .= $tmpArr[$i] . "</br>";
}
return $out;
}
Если ваши строки разделены CR или LF, вы можете попробовать взорвать свою переменную $data:
$lines = explode("\n", $data);
$lines должно быть массивом, и вы можете определить количество записей с помощью sizeof() и просто получить последние 5.
это прочитанная последняя строка из текстового файла
$data = array_slice(file('logs.txt'),10);
foreach ($data as $line)
{
echo $line."<br/>";
}