2011-06-23 38 views
11

我可以從PHP讀取一個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,但我只需要閱讀的最後幾行。我該怎麼做?

謝謝

+0

可能重複:[PHP - 從文本文件末尾讀取](http:// stackoverflow。com/questions/5697717) – hippietrail

回答

5

這不是純粹的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); 
?> 
+0

+1非常整潔,我對命令行非常陌生! –

+0

但是它會影響真實文件還是隻是命令實際上? – kritya

+0

它不會影響真實文件,但會創建一個新文件/tmp/myfilereversed.txt,因此您需要將其全部刪除 – Greenisha

6

這取決於你如何解釋「可以」。

如果您不知道是否可以直接使用(使用PHP函數)而不讀取所有前面的行,那麼答案是:,您不能。

行尾是數據的解釋,如果您實際讀取數據,則只能知道它們在哪裏。

如果它是一個非常大的文件,我不會那樣做。 如果您從頭開始掃描文件,並逐漸從文件結尾讀取文件,會更好。

更新

這裏的讀取文件的最後ñ線不經過這一切閱讀PHP只方式:

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); 
} 
+0

你完全可以做到這一點,而無需閱讀前面的所有內容,正如你自己在最後一句中所建議的那樣。 :) – awgy

+0

@awgy:我的意思是直接使用PHP函數或來自操作系統的幫助;)也許我措辭不佳:) – phant0m

+0

@kritya,@awgy:我添加了我描述的實現。 – phant0m

15

您可以使用fopen和fseek從文件末尾向後導航。例如

$fp = @fopen($file, "r"); 
$pos = -2; 
while (fgetc($fp) != "\n") { 
    fseek($fp, $pos, SEEK_END); 
    $pos = $pos - 1; 
} 
$lastline = fgets($fp); 
+0

它只是正常的閱讀方式-.- – kritya

+0

通過將fseek與負數偏移量和SEEK_END,您將位置指示器設置爲在**文件結尾之前定位$ offset字節**,因此您不需要從文件的開頭讀取 – Greenisha

+0

如果文件以換行符結束,則此片段將只是返回換行符。另外,我相信'$ pos'應該在循環開始之前初始化爲'-1'。 – awgy

2

如果您的代碼無法正常工作並報告錯誤,則應在您的帖子中包含錯誤!

您收到錯誤的原因是因爲您試圖將整個文件的內容存儲在PHP的內存空間中。

解決問題的最有效的方法是Greenisha建議並尋求文件的結尾然後回退一點。但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"); 
+0

只是更多的解釋會非常好,你認爲+1取消了它的想法。 – kritya

+0

您的解決方案還會遍歷整個文件,而fgets和fseek的開銷會稍微慢一些。 – stefgosselin

+0

@stefgosselin:no - 再次讀取它 - 它只遍歷文件末尾的塊,該塊與要提取的數據大小相同。 – symcbean

1

下面是另一種解決方案。它沒有在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); 
-1

正如愛因斯坦所說的每一件事應儘可能簡單,但不簡單。在這一點上,您需要一個數據結構,一個LIFO數據結構或者只需要一個堆棧。

2

對於Linux,你可以做

$linesToRead = 10; 
exec("tail -n{$linesToRead} {$myFileName}" , $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); 
0

那麼在檢索同樣的事情,我可以穿越以下,並認爲它可能對其他人有用,所以慚愧這裏摹吧:

/*讀取從線端線文件*/

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); 

    } 
0

如果你知道行有多長,就可以避免很多的黑魔法,只是搶一大塊文件末尾。

我需要一個非常大的日誌文件的最後15行,總共約3000個字符。所以我只是抓住最後的8000個字節來安全起見,然後像平常一樣讀取文件,並從最後拿出我需要的。

$fh = fopen($file, "r"); 
    fseek($fh, -8192, SEEK_END); 
    $lines = array(); 
    while($lines[] = fgets($fh)) {} 

這可能甚至比最高級別的答案,這由字符讀取文件字符更有效率,比較每個字符,並且基於換行符分裂。