3

ここ数日、ファイルの終わりの前に feof() 関数が true を返すという奇妙な PHP の問題に取り組んでいます。以下は私のコードのスケルトンです:

$this->fh = fopen("bigfile.txt", "r");    

while(!feof($this->fh))
{
    $dataString = fgets($this->fh);

    if($dataString === false && !feof($this->fh))
    {
        echo "Error reading file besides EOF";
    }
    elseif($dataString === false && feof($this->fh))
    {
        echo "We are at the end of the file.\n";

        //check status of the stream
        $meta = stream_get_meta_data($this->fh);
        var_dump($meta);
    }
    else
    {
        //else all is good, process line read in 
    }
}

多くのテストを通じて、プログラムは 1 つのファイルを除いてすべて正常に動作することがわかりました。

  • ファイルはローカル ドライブに保存されます。
  • このファイルは約 800 万行の長さで、平均して 1 行あたり約 200 ~ 500 文字です。
  • クリーニング済みで、ヘキサエディタで精査したところ、異常な文字は見つかりませんでした。
  • プログラムは、ファイルの最後に達したと思われる場合 (約 800K 行が残っているにもかかわらず)、7172714 行で一貫して失敗します。
  • 1 行あたりの文字数が少ないが 2000 万から 3000 万行のファイルでプログラムをテストしましたが、問題はありませんでした。
  • http://php.net/manual/en/function.fgets.phpのコメントからコードを実行してみましたが、問題を引き起こしているのが自分のコードの何かであり、サードパーティのコードが同じで失敗したかどうかを確認するためだけですライン。編集: サードパーティのコードが fgets() の代わりに fread() を使用したことも言及する価値があります。
  • fgets 関数でいくつかのバッファ サイズを指定しようとしましたが、どれも違いはありませんでした。

var_dump($meta) からの出力は次のとおりです。

 array(9) {
  ["wrapper_type"]=>
  string(9) "plainfile"
  ["stream_type"]=>
  string(5) "STDIO"
  ["mode"]=>
  string(1) "r"
  ["unread_bytes"]=>
  int(0)
  ["seekable"]=>
  bool(true)
  ["uri"]=>
  string(65) "full path of file being read"
  ["timed_out"]=>
  bool(false)
  ["blocked"]=>
  bool(true)
  ["eof"]=>
  bool(true)
}

ファイルの終わりの前に feof が true を返す原因を突き止めようとすると、次のいずれかを推測する必要があります。

A) 何かが原因で fopen ストリームが失敗し、何も読み取れません (feof が true を返す)

B) いっぱいになって大混乱を引き起こしているバッファがどこかにある

C) PHP の神々は怒っている

他の誰かがこの問題を抱えているかどうかを広く検索しましたが、ファイルがバイナリ モードではなくテキスト モードで読み込まれ、問題を引き起こしていた C++ 以外のインスタンスを見つけることができませんでした。

更新: 読み取り関数が繰り返された回数と、その横にあるエントリに関連付けられたユーザーの一意の ID を常にスクリプトに出力させました。スクリプトは 7175502 のうちの 7172713 行の後でまだ失敗していますが、ファイル内の最後のユーザーの一意の ID が 7172713 行に表示されています。問題は、何らかの理由で行がスキップされて読み取られていないようです。すべての改行が存在します。

4

3 に答える 3

2

fgets() は、コンテンツが空であるいくつかの行をランダムに読み取っているようです。エラーチェックの方法(およびサードパーティコードでのエラーチェックの記述方法)が原因で、行番号が読み取られていることを示すテストが遅れていても、スクリプトは実際にはファイルの最後に到達します。ここで本当の問題は、fgets() と fread() が行が空ではないのに空であると考える原因です。話題が変わりますので、別の質問として質問させていただきます。ご協力ありがとうございました!

また、誰もハングしたままにしないように、サードパーティのコードが機能しなかった理由は、少なくとも fgets と fread が空の文字列を返すという現在の問題がスクリプトに何を与えない改行がある行に依存していたためです。行が存在したことを知る必要があるため、ファイルの末尾を超えて実行を試み続けます。以下は、実行速度に基づいて優れていると私が考える、わずかに変更されたサード パーティのスクリプトです。

元のスクリプトは、http: //php.net/manual/en/function.fgets.phpのコメントにあります。

<?php

//File to be opened
$file = "/path/to/file.ext";
//Open file (DON'T USE a+ pointer will be wrong!)
$fp = fopen($file, 'r');
//Read 16meg chunks
$read = 16777216;
//\n Marker
$part = 0;

while(!feof($fp))
{
    $rbuf = fread($fp, $read);
    for($i=$read;$i > 0 || $n == chr(10);$i--)
    {
        $n=substr($rbuf, $i, 1);
        if($n == chr(10))break;
        //If we are at the end of the file, just grab the rest and stop loop
        elseif(feof($fp))
        {
            $i = $read;
            $buf = substr($rbuf, 0, $i+1);
            echo "<EOF>\n";
            break;
        }
    }
    //This is the buffer we want to do stuff with, maybe thow to a function?
    $buf = substr($rbuf, 0, $i+1);

    //output the chunk we just read and mark where it stopped with <break>
    echo $buf . "\n<break>\n";

    //Point marker back to last \n point
    $part = ftell($fp)-($read-($i+1));
    fseek($fp, $part);
}
fclose($fp);

?>

更新: さらに何時間もかけて検索、分析、毛抜きなどを行った結果、犯人は捕らえられていない悪いキャラクター (この場合は 1/2 文字の 16 進値 BD) だったようです。スクリプトから読み取っていたファイルを生成するときに、元のソースから行を読み取るために stream_get_line() を使用しました。次に、すべての悪い文字を削除し(私の正規表現が標準に達していないようです)、 str_getcsv() を使用してコンテンツを配列に変換し、何らかの処理を行ってから、新しいファイルに書き込みます(私が持っていたもの)読んでみる)。このプロセスのどこかで、おそらく str_getcsv() で、1/2 文字が原因で、データの代わりに空白行が挿入されました。これらのうち数千個がファイル全体に配置されていました (1/2 記号が表示されている場所はどこでも)。これにより、ファイルが正しい長さのように見えました。ただし、既知の行数に基づいて入力をカウントすると、EOF に到達するのが早すぎます。この問題で私を助けてくれたすべての人に感謝したいと思います。本当の原因が私の質問とは何の関係もなかったことを非常に残念に思います。しかし、皆さんからの提案や質問がなければ、適切な場所を探すことはできなかったでしょう。

この経験から学んだ教訓 - EOF に到達するのが早すぎる場合、探すのに最適な場所は二重改行のインスタンスです。フォーマットされたファイルから読み取るスクリプトを作成するときは、これらを確認することをお勧めします。以下は、それを行うために変更された元のコードです。

$this->fh = fopen("bigfile.txt", "r");    

while(!feof($this->fh))
{
    $dataString = fgets($this->fh);

    if($dataString == "\n" || $dataString == "\r\n" || $dataString == "")
    {
        throw new Exception("Empty line found.");
    }

    if($dataString === false && !feof($this->fh))
    {
        echo "Error reading file besides EOF";
    }
    elseif($dataString === false && feof($this->fh))
    {
        echo "We are at the end of the file.\n";

        //check status of the stream
        $meta = stream_get_meta_data($this->fh);
        var_dump($meta);
    }
    else
    {
        //else all is good, process line read in 
    }
}
于 2015-01-14T06:57:25.577 に答える