web-dev-qa-db-ja.com

ファイルから最後の行を読み取る

私は問題にぶつかっています。いくつかの実行中のプロセスからの出力が書き込まれるLinuxボックスにログがあります。このファイルはときどき大きくなることがあり、そのファイルの最後の行を読み取る必要があります。

問題は、このアクションがAJAXリクエストを介してかなり頻繁に呼び出され、そのログのファイルサイズが5〜6MBを超えると、サーバーに適さないことです。それで、最後の行を読み取る必要がありますが、ファイル全体を読み取ってパススルーするか、RAM)にロードする必要はありません。これは、ボックスをロードするためにロードされるだけだからです。

この操作がスムーズに実行され、サーバーに悪影響を与えたりApacheを強制終了したりしないように、最適化はありますか?

私が持っている他のオプションはexec('tail -n 1 /path/to/log')ですが、それはそれほど良く聞こえません。

後で編集:ファイルが巨大になる可能性があるため、RAMにファイルを入れたくありません。fopen()はオプションではありません。

30

これはうまくいくはずです:

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

echo $line;
44
Ionuț G. Stan

fseek を使用します。 "\ n"が見つかるまで、最後の位置までシークし、後方にシークします(現在の位置を伝えるには ftell を使用します)。


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

注:私はテストしていません。調整が必要な場合があります。

更新:空のファイルについて指摘してくださったSyntax Errorに感謝します。

:-D

UPDATE2:別の構文エラーを修正、$LastLine = ""にセミコロンがない

17
NawaMan

fseek 関数を探しています。ファイルの最後の行を読み取る方法の実例は、コメントセクションにあります。

5
slikts

行の長さの上限がわかっている場合は、次のようにすることができます。

$maxLength = 1024;
$fp = fopen('somefile.txt', 'r');
fseek($fp, -$maxLength , SEEK_END); 
$fewLines = explode("\n", fgets($fp, $maxLength));
$lastLine = $fewLines[count($fewLines) - 1];

編集への応答:fopenはファイルへのハンドルを取得するだけです(つまり、ファイルが存在すること、プロセスに権限があること、プロセスがファイルを使用していることをosに通知するなど)。この例では、ファイルから1024文字だけがメモリに読み込まれます。

3
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

3
Daniel A. White

これは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');

あなたはその最後の行を取得します

あなたの問題は this one に似ています

ファイル全体をメモリに読み込まないようにする最善の方法は次のようです。

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

これを反対側から最適化することは可能でしょうか?もしそうなら、それを切り捨てる間、ロギングアプリケーションが常にその行をファイルに記録するようにします(つまり、>>の代わりに>)。

いくつかの最適化は「推測」によって達成される可能性がありますが、ファイルを開くだけで、平均ログ行幅を使用して、最後の行がどこにあるかを推測できます。 fseekでその位置にジャンプし、最後の行を見つけます。

0
Wolph

これは、返される行数を指定できる関数にラップされた、ここでの回答のコンパイルです。

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;
}
0
DynamicDan

これはループが1つだけの私のソリューションです

        $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
caiofior

http://php.net/manual/en/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
Syntax Error