PHP:试图让 fgets() 在 CRLF、CR 和 LF 上触发
PHP: trying to get fgets() to trigger both on CRLF, CR and LF
我正在使用 proc_open 和 fgets($stdout) 读取 PHP 中的流,试图在输入时获取每一行。
许多 linux 程序(包管理器、wget、rsync)只使用 CR(回车符 return)字符来表示定期更新 "in place" 的行,例如下载进度。我想在这些更新发生时立即捕获它们(作为单独的行)。
目前,fgets($stdout) 一直读取直到 LF,所以当进度非常缓慢时(例如大文件)它会一直读取直到完全完成,在 return 之前将所有更新的行作为一个长字符串,包括 CR。
我已经尝试设置 "mac" 选项来将 CR 检测为行尾:
ini_set('auto_detect_line_endings',true);
但这似乎不起作用。
现在,stream_get_line 允许我将 CR 设置为换行符,但不能将 CRLF、CR 和 LF 都视为分隔符的 "catch all" 解决方案。
我当然可以阅读整行,使用各种 PHP 方法将其拆分并用 LF 替换所有类型的换行符,但它是一个流,我希望 PHP 能够在它仍然 运行.
时获得进度指示
所以我的问题是:
我如何从 STDOUT 管道(从 proc_open)读取直到发生 LF 或 CR,而不必等到整行都进入?
提前致谢!
解决方案:
我使用 Fleshgrinder 的过滤器 class 将流中的 \r 替换为 \n(参见已接受的答案),并将 fgets() 替换为 fgetc() 以获得更多 "realtime" 访问内容标准输出:
$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
}
在使用流之前使用流过滤器规范化换行符。我根据 stream_filter_register
.
上 PHP 的手册页中的示例创建了以下代码应该可以解决问题
代码未经测试!
<?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
使用行缓冲区,Apple 可能也在做同样的事情。另一方面,大多数 STDERR
流是无缓冲的。您可以尝试将程序的输出重定向到另一个管道(例如 STDERR
或任何其他管道),看看您是否更幸运。
我正在使用 proc_open 和 fgets($stdout) 读取 PHP 中的流,试图在输入时获取每一行。
许多 linux 程序(包管理器、wget、rsync)只使用 CR(回车符 return)字符来表示定期更新 "in place" 的行,例如下载进度。我想在这些更新发生时立即捕获它们(作为单独的行)。
目前,fgets($stdout) 一直读取直到 LF,所以当进度非常缓慢时(例如大文件)它会一直读取直到完全完成,在 return 之前将所有更新的行作为一个长字符串,包括 CR。
我已经尝试设置 "mac" 选项来将 CR 检测为行尾:
ini_set('auto_detect_line_endings',true);
但这似乎不起作用。
现在,stream_get_line 允许我将 CR 设置为换行符,但不能将 CRLF、CR 和 LF 都视为分隔符的 "catch all" 解决方案。
我当然可以阅读整行,使用各种 PHP 方法将其拆分并用 LF 替换所有类型的换行符,但它是一个流,我希望 PHP 能够在它仍然 运行.
时获得进度指示所以我的问题是:
我如何从 STDOUT 管道(从 proc_open)读取直到发生 LF 或 CR,而不必等到整行都进入?
提前致谢!
解决方案:
我使用 Fleshgrinder 的过滤器 class 将流中的 \r 替换为 \n(参见已接受的答案),并将 fgets() 替换为 fgetc() 以获得更多 "realtime" 访问内容标准输出:
$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
}
在使用流之前使用流过滤器规范化换行符。我根据 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
使用行缓冲区,Apple 可能也在做同样的事情。另一方面,大多数 STDERR
流是无缓冲的。您可以尝试将程序的输出重定向到另一个管道(例如 STDERR
或任何其他管道),看看您是否更幸运。