You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 03:14:59