PHP:尝试使fgets()在CRLF、CR和LF上触发

5
我正在使用proc_open和fgets($stdout)在PHP中读取流,尝试获取每一行数据。
许多Linux程序(例如包管理器、wget和rsync)仅使用回车符(CR)作为在某些情况下“原地”更新的行,例如下载进度。我想立即捕获这些更新(作为单独的行)。
目前,fgets($stdout)会一直读取,直到LF字符,因此当进度非常缓慢时(例如大文件),它会一直读取直到完全完成,然后将所有更新的行作为一个长字符串返回,其中也包括CR字符。
我已尝试设置"mac"选项以检测CR字符作为行结束符:
ini_set('auto_detect_line_endings',true); 

但似乎这并不起作用。
现在,stream_get_line可以让我将CR设置为换行符,但不能将CRLF、CR和LF统一视为分隔符。
我当然可以读取整行,使用各种PHP方法拆分它,并将所有类型的换行符替换为LF,但这是一个流,并且我希望PHP能够在运行时获得进度指示。
所以我的问题是:
如何从STDOUT管道(来自proc_open)读取,直到出现LF或CR,而不必等到整行完整?
先感谢您!
解决方案:
我使用了Fleshgrinder的过滤器类来在流中将\r替换为\n(请参见接受的答案),并将fgets()替换为fgetc()以更实时地访问STDOUT的内容:
$stdout = $proc->pipe(1);
stream_filter_register("EOL", "EOLStreamFilter");
stream_filter_append($stdout, "EOL"); 

while (($o = fgetc($stdout))!== false){
    $out .= $o;                            // buffer the characters into line, until \n.
    if ($o == "\n"){echo $out;$out='';}    // can now easily wrap the $out lines in JSON
}

2
你确定这不仅仅是I/O缓存吗?许多文件系统并非按字节写入磁盘,而是将写入缓存,直到有足够数量的块可以一次性物理写入;同样,在许多情况下也会使用缓冲输出。 - Mark Baker
但是,如果我在CLI上运行类似于wget server.com/file的命令,它会直接输出更新,就像从屏幕宽度的1%[==> ]行到2%[==> ]一样,只要发生就会立即显示。或者,从PHP中的STDOUT管道读取与终端显示的基本不同吗? - okdewit
1个回答

2

在处理流之前,使用流过滤器来规范化换行符。根据PHP手册页面上关于stream_filter_register的示例,我创建了以下代码,应该可以解决问题。

代码未经测试!

<?php

// https://php.net/php-user-filter
final class EOLStreamFilter extends php_user_filter {

    public function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = str_replace([ "\r\n", "\r" ], "\n", $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }

}

stream_filter_register("EOL", "EOLStreamFilter");

// Open stream …

stream_filter_append($yourStreamHandle, "EOL");

// Perform your work with normalized EOLs …

编辑: Mark Baker 在您的问题中发布的评论是正确的。大多数Linux发行版都使用STDOUT的行缓冲区,苹果也可能是这样做的。另一方面,大多数STDERR流是无缓冲的。您可以尝试将程序的输出重定向到另一个管道(例如STDERR或任何其他),看看是否有更好的运气。


刚刚测试了你的类扩展,它可以用于将CR替换为LF!真是个好技巧 :) STDOUT的行缓冲仍然是一个问题,所以我要尝试将其管道传输到其他地方,看看会发生什么。 但至少现在输出在html <pre>标签中显示换行符(我使用ajax进度事件,关闭PHP缓冲),因此过滤有效。 另外,奇怪的是,函数“stream_bucket_make_writable”应该是“stream_bucket_make_writeable”,与语言风格指南和其他带有writable单词的PHP函数不同。 - okdewit
1
好知道,如果重定向有助于缓冲,请告诉我。XD 典型的 PHP! - Fleshgrinder
1
找到解决方案了!经过一些测试,似乎 STDOUT 缓冲不是问题,它直接输出到 PHP。过滤器也只提供了部分解决方案:它替换了 \r,但仍然在读取完行后才进行替换。所以我遇到了 fgetc() 函数,它将所有字节作为流读取,而不是按行读取。您的过滤器类仍然可以很好地清理流,将 \r 转换为 \n。这样我就可以自己实现缓冲——只需附加一个字符串,直到看到换行符,将其包装为 JSON(用于元数据)并输出!我会在休息一段时间后将解决方案添加到我的问题中。 - okdewit
1
非常感谢您的评论,我从中学到了很多东西,真正帮助我找到了正确的方向! - okdewit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接