Ubuntu20下PHP7.4调用带交互Bash脚本遇SSH密码输入问题求助
问题:PHP执行带密码输入的SSH命令时无法交互
我查了几天论坛并参考ChatGPT建议,写了一段能处理多数命令的PHP代码,但执行需要密码输入的SSH命令时,脚本无法与打开的Bash实例交互,命令输出直接绕过PHP脚本打印到控制台。
现有PHP代码
#!/usr/bin/php <?php function OpenCLI(){ $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $process = proc_open("/bin/bash", $descriptorspec, $pipes, null, null, array('tty'=>true));//'tty'=>true so that pseudo-terminal is used so can talk to ssh command stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); return array("process" => $process, "pipes" => $pipes); } function CLIExists($cli){ return is_resource($cli["process"]); } function CLIWrite($cli,$text){ fwrite($cli["pipes"][0], $text); } function CLIRead($cli,$max_ms=100, $max_len=10000){ $ms=0; $str=""; while(true){ $new=fread($cli["pipes"][1], 1024); if(strlen($new)>0){ $str.=$new; //print "new: '" . $new . "'"; $ms=0; } else{ $ms++; //print "ms: '" . $ms . "'"; } if($ms>$max_ms || strlen($str)>$max_len) break; usleep(1000); } return $str; }
SSH测试代码
$cli=OpenCLI(); if(CLIExists($cli)){ CLIWrite($cli,"ssh admin@10.10.74.22\n"); $res1=CLIRead($cli); CLIWrite($cli,"[password]\n"); $res2=CLIRead($cli); print "res1: '$res1'\n"; print "res2: '$res2'\n"; }
控制台输出
res1: '' res2: '' admin@10.10.74.22's password: /dustin# admin@10.10.74.22's password: admin@10.10.74.22's password:
需求说明
原本计划使用SH2.php,但测试中它会随机失效,不想花费大量时间调试该库。需要实现远程设备自动化更新,同时希望能轻松自动化所有可通过手动输入命令完成的操作,也接受Python或Node.js的相关实现方案。
解决方案
PHP修复方案
问题核心是SSH检测到伪终端后,会直接与终端交互而非管道输出,同时原代码的非阻塞设置和单一管道读取逻辑无法适配交互式场景。修改后的代码如下:
#!/usr/bin/php <?php function OpenCLI(){ $descriptorspec = array( 0 => array("pty"), // 用伪终端替代管道作为stdin 1 => array("pty"), // 用伪终端替代管道作为stdout 2 => array("pty") // 用伪终端替代管道作为stderr ); $process = proc_open("/bin/bash", $descriptorspec, $pipes, null, null); // 恢复阻塞模式,伪终端交互需要同步操作 stream_set_blocking($pipes[0], 1); stream_set_blocking($pipes[1], 1); stream_set_blocking($pipes[2], 1); return array("process" => $process, "pipes" => $pipes); } function CLIExists($cli){ return is_resource($cli["process"]); } function CLIWrite($cli,$text){ fwrite($cli["pipes"][0], $text); fflush($cli["pipes"][0]); // 强制刷新输出,确保命令立即发送 } function CLIRead($cli,$timeout_sec=2, $max_len=10000){ $start_time = time(); $str = ""; while(true){ // 同时读取stdout和stderr,SSH密码提示通常输出到stderr $stdout = fread($cli["pipes"][1], 1024); $stderr = fread($cli["pipes"][2], 1024); if(strlen($stdout) > 0) $str .= $stdout; if(strlen($stderr) > 0) $str .= $stderr; // 超时或达到最大长度时退出循环 if((time() - $start_time) > $timeout_sec || strlen($str) > $max_len) break; usleep(100000); // 短暂休眠避免资源占用过高 } return $str; } // 测试代码 $cli=OpenCLI(); if(CLIExists($cli)){ CLIWrite($cli,"ssh admin@10.10.74.22\n"); $res1=CLIRead($cli); echo "res1: '$res1'\n"; CLIWrite($cli,"[password]\n"); $res2=CLIRead($cli); echo "res2: '$res2'\n"; }
关键修改点
- 用
pty伪终端替代管道,让SSH认为处于真实终端环境 - 恢复阻塞模式,适配伪终端的同步交互逻辑
- 同时读取stdout和stderr,捕获SSH密码提示
- 添加
fflush确保命令立即发送到伪终端
Python实现方案
使用pexpect库专门处理交互式命令行场景,逻辑更简洁:
import pexpect # 启动SSH会话 child = pexpect.spawn('ssh admin@10.10.74.22') # 捕获密码提示并输入密码 child.expect('password:') child.sendline('[password]') # 捕获登录后的提示符(根据实际环境修改) child.expect('#') # 执行后续命令 child.sendline('ls -l') child.expect('#') # 打印命令输出 print(child.before.decode('utf-8')) # 关闭会话 child.close()
安装依赖
pip install pexpect
Node.js实现方案
使用node-pty库创建伪终端,处理交互式场景:
const pty = require('node-pty'); // 启动伪终端Shell const shell = pty.spawn('/bin/bash', [], { name: 'xterm-color', cols: 80, rows: 30, cwd: process.cwd(), env: process.env }); // 监听终端输出 shell.on('data', (data) => { console.log('终端输出:', data); // 检测到密码提示时输入密码 if (data.includes('password:')) { shell.write('[password]\n'); } }); // 发送SSH命令 shell.write('ssh admin@10.10.74.22\n'); // 延时发送后续命令(示例) setTimeout(() => { shell.write('ls -l\n'); }, 3000);
安装依赖
npm install node-pty
内容的提问来源于stack exchange,提问作者Dustin Soodak
相关产品推荐
相关产品推荐

