PHP:尝试让fgets()在CRLF,CR和LF上触发

问题描述:

I'm reading streams in PHP, using proc_open and fgets($stdout), trying to get every line as it comes in.

Many linux programs (package managers, wget, rsync) just use a CR (carriage return) character for lines which periodically updates "in place", like download progress. I'd like to catch these updates (as separate lines) as soon as they happen.

At the moment, fgets($stdout) just keeps reading until a LF, so when progress is going very slowly (big file for example) it just keeps on reading until it's completely done, before returning all the updated lines as one long string, including the CRs.

I've tried setting the "mac" option to detect CRs as line endings:

ini_set('auto_detect_line_endings',true); 

But that doesn't seem to work.

Now, stream_get_line would allow me to set CRs as line breaks, but not a "catch all" solution which treats both CRLF, CR and LF as delimiters.

I could of course read the whole line, split it using various PHP methods and replace all types of linebreaks with LFs, but it's a stream, and I want PHP to be able to get an indication of progress while it's still running.

So my question:

How can I read from the STDOUT pipe (from proc_open) until a LF or CR happens, without having to wait until the whole line is in?

Thanks in advance!

Solution:

I used Fleshgrinder's filter class to replace with in the stream (see accepted answer), and replaced fgets() with fgetc() to get more "realtime" access to contents of 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 
.
    if ($o == "
"){echo $out;$out='';}    // can now easily wrap the $out lines in JSON
}

我正在使用proc_open和fgets($ stdout)读取PHP中的流,尝试获取每一行 进来了。 p>

许多Linux程序(包管理器,wget,rsync)只使用CR(回车)字符来定期更新“就地”,例如下载进度。 我想在它们发生时立即捕获这些更新(作为单独的行)。 p>

目前,fgets($ stdout)只是继续读取直到LF,所以当进度是 进展非常缓慢(例如大文件)它只是继续阅读直到它完全完成,然后将所有更新的行作为一个长字符串返回,包括 em> CR。 p> 我尝试设置“mac”选项来检测CR为行结尾: p>

  ini_set('auto_detect_line_endings',true);  
  code>  pre> 
 
 

但这似乎不起作用。 p>

现在,stream_get_line允许我将CR设置为换行符,但不能将“全部捕获”解决方案设置为将CRLF,CR和LF视为分隔符。 p>

我当然可以阅读整行,使用各种PHP方法拆分它并用LF替换所有类型的换行符,但它是一个流,我希望PHP能够得到一个 当它仍然在运行时显示进度。 p>

所以我的问题: strong> p>

如何从STDOUT管道读取( 从proc_open开始直到LF 或 strong> CR发生,而不必等到整行都在? p>

提前致谢! p> \ n

解决方案: h2>

我使用Fleshgrinder的过滤器类将 替换为流中的 (请参阅接受的答案),并用fgetc()替换fgets()以获得更多 “实时”访问STDOUT的内容: p>

  $ stdout = $ proc-> pipe(1); 
stream_filter_register(“EOL”,“EOLStreamFilter”); 
stream_filter_append  ($ stdout,“EOL”);  
 
而(($ o = fgetc($ stdout))!== false){
 $ out。= $ o;  //将字符缓冲到行中,直到
。
 if($ o ==“
”){echo $ out; $ out ='';} //现在可以轻松地将$ out行包装在JSON中 n} 
  code>  pre> 
  div>

Use a stream filter to normalize your new line characters before consuming the stream. I created the following code that should do the trick based on the example from PHP’s manual page on stream_filter_register.

Code is untested!

<?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([ "
", "" ], "
", $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 …

EDIT: The comment Mark Baker posted on your question is true. Most Linux distributions are using a line buffer for STDOUT and it is possible that Apple is doing the same. On the other hand most STDERR streams are unbuffered. You could try to redirect the output of the program to another pipe (e.g. STDERR or any other) and see if you have more luck with that.