如何使用exec执行多步骤交互式Bash命令?
解决PHP调用交互式命令行脚本的阻塞问题
我有一个多步骤的交互式命令行脚本,执行时会依次提问,需要在PHP代码里调用它:
php command.php > What is your name? User Smith > What is your age? 25 > You are old enough!
但用exec调用时程序会卡住,我想实现类似这样的链式调用效果:
<?php $result = exec('php command.php')->next('User Smith')->next('25'); if ($result->response === 'You are old enough!') echo "Yahoo"; else echo "Oh no!";
问题原因
exec函数只能单向读取进程的标准输出,无法向进程的标准输入写入内容,所以遇到交互式脚本需要输入时,进程会一直等待输入,导致PHP代码卡住。
解决方案:用proc_open实现双向通信
proc_open可以创建子进程并打开标准输入、输出、错误的管道,让我们能主动给脚本输入内容,同时读取输出。下面是封装好的实现,能满足你想要的链式调用需求:
<?php class InteractiveCommand { private $process; private $pipes; private $output = ''; public static function exec($command) { $descriptorSpec = [ 0 => ['pipe', 'r'], // 标准输入,可写 1 => ['pipe', 'w'], // 标准输出,可读 2 => ['pipe', 'w'] // 标准错误,可读 ]; $process = proc_open($command, $descriptorSpec, $pipes); if (!is_resource($process)) { throw new Exception('无法启动进程'); } // 非阻塞读取,避免一开始就卡住 stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $instance = new self(); $instance->process = $process; $instance->pipes = $pipes; return $instance; } public function next($input) { // 写入输入内容,加换行符模拟回车 fwrite($this->pipes[0], $input . PHP_EOL); // 短时间等待输出生成,实际可根据输出内容判断时机 usleep(50000); // 读取当前输出 $this->output .= stream_get_contents($this->pipes[1]); $this->output .= stream_get_contents($this->pipes[2]); return $this; } public function getResponse() { // 关闭输入,让进程执行完毕 fclose($this->pipes[0]); // 读取剩余输出 $this->output .= stream_get_contents($this->pipes[1]); $this->output .= stream_get_contents($this->pipes[2]); // 关闭管道并等待进程结束 fclose($this->pipes[1]); fclose($this->pipes[2]); proc_close($this->process); // 提取最后一行输出(可根据脚本输出格式调整) $lines = explode(PHP_EOL, trim($this->output)); return end($lines); } } // 使用示例 $result = InteractiveCommand::exec('php command.php') ->next('User Smith') ->next('25'); $response = $result->getResponse(); if ($response === 'You are old enough!') { echo "Yahoo"; } else { echo "Oh no!"; }
代码说明
proc_open配置:定义三个管道对应标准输入、输出、错误流,实现双向通信。- 非阻塞流设置:避免读取输出时因进程未生成内容而卡住。
- 链式调用:
exec启动进程并返回实例,next写入输入并读取输出,最后getResponse关闭资源并提取最终结果。 - 等待逻辑:
usleep是简单的等待方式,实际场景可循环读取直到出现预期提示(比如> What is your age?),这样更可靠。
内容的提问来源于stack exchange,提问作者AidOnline01
相关产品推荐
相关产品推荐

