PHP feof()在文件结束前返回true

时间:2015-01-14 05:10:36

标签: php fopen fgets feof

在过去几天,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 
    }
}

通过大量测试,我发现该程序在除一个文件之外的所有内容上都能正常工作:

  • 该文件存储在本地驱动器上。
  • 此文件长约800万行,平均每行大约200-500个字符。
  • 已经使用十六进制编辑器清理并仔细检查,未发现任何异常字符。
  • 当程序认为已到达文件末尾时(即使它还有~800K行),该程序始终在第7172714行失败。
  • 我已经对每行字符数较少但文件数量在2000万到3000万之间的文件进行了测试。
  • 我尝试运行http://php.net/manual/en/function.fgets.php上的评论中的代码,只是为了查看我的代码中是否存在导致问题并且第三方代码在同一行上失败的内容。编辑:另外值得一提的是,第三方代码使用了fread()而不是fgets()。
  • 我尝试在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行。似乎问题是由于某种原因被跳过而且未被读取。所有换行都存在。

2 个答案:

答案 0 :(得分:4)

你必须拆分文件或增加php中的超时 由:

upload_max_filesize = 2M 
;or whatever size you want

max_execution_time = 60    ;如果你必须

,也要更高

,因为:  如果文件指针处于EOF或发生错误(包括套接字超时),则返回TRUE;否则返回FALSE。 见:http://php.net/manual/en/function.feof.php

答案 1 :(得分: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字符的十六进制值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 
    }
}