如何在PHP调用CLI程序时透传输出并执行自定义函数(旧方法失效)
问题根因
- 原有代码存在逻辑错误:仅在循环外执行了1次
fgets()读取流内容,循环体内没有重新读取新的流数据,导致循环始终复用第一次读取的内容,出现第一行无限重复的问题。 - yt-dlp会自动检测输出目标类型:当输出目标是交互式终端(如
cmd.exe)时会实时刷新输出,当输出目标是管道(被PHP的popen调用时属于管道输出)时会默认开启全缓冲,且进度类输出默认用回车符\r而非换行符\n实现同屏刷新,而fgets()默认以\n作为读取结束标记,因此无法实时读取单行进度内容。 - PHP 8.0.13的输出缓冲、流处理底层逻辑有细微调整,放大了上述问题的影响。
修复方案
第一步:调整yt-dlp调用参数
在原$yt_dlp_command中加入以下参数:--progress-template "%(progress._percent_str)s %(progress._eta_str)s" --progress-newline --quiet --no-colors
以上参数强制yt-dlp用换行输出进度、关闭颜色转义符、使用固定模板输出进度内容,避免非TTY场景下的缓冲问题。
第二步:修复PHP读取逻辑
修复后的代码如下:
// 关闭PHP顶层输出缓冲,避免多层缓冲导致无法实时输出 while (ob_get_level() > 0) { ob_end_clean(); } $a = popen($yt_dlp_command, 'r'); if ($a) { // 设置流为非阻塞模式,避免fread卡住 stream_set_blocking($a, false); while (!feof($a)) { // 每次读取任意长度的可用内容,不等待换行符 $row_data = fread($a, 1024); if ($row_data !== false && $row_data !== '') { // 在这里执行你的自定义函数 my_function(); echo $row_data; // 强制刷新输出缓冲 flush(); } // 避免CPU空转,可根据需求调整等待时长 usleep(100000); } pclose($a); }
可选兼容方案
如果上述方案仍不生效,可通过proc_open替代popen,显式指定输出为PTY(伪终端),模拟交互式终端环境,让yt-dlp按照终端场景的逻辑输出内容:
$descriptorspec = [ 0 => ['pty'], // 标准输入为伪终端 1 => ['pty'], // 标准输出为伪终端 2 => ['pty'] // 错误输出为伪终端 ]; $process = proc_open($yt_dlp_command, $descriptorspec, $pipes); if (is_resource($process)) { stream_set_blocking($pipes[1], false); while (!feof($pipes[1])) { $row_data = fread($pipes[1], 1024); if ($row_data) { my_function(); echo $row_data; flush(); } usleep(100000); } proc_close($process); }
内容的提问来源于stack exchange,提问作者user17535142
相关产品推荐
相关产品推荐

