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

PHP远程执行SSH脚本:获取stderr并判断脚本执行完成时机

解决PHP SSH执行远程脚本的结束判断与stderr捕获问题

首先,咱们拆解下你的核心需求:通过PHP的ssh2扩展执行远程脚本,既要精准捕获标准错误输出(stderr),又要准确判断脚本何时执行完毕,才能拿到完整的输出结果。你现有代码里用了后台执行(&)和输出重定向到文件,这其实会让追踪脚本状态变得麻烦,咱们一步步调整优化:

方案1:同步执行脚本(阻塞等待结束,直接捕获输出)

如果你的脚本执行时长不是特别久,不建议后台执行,直接同步执行的话,PHP会自动阻塞直到远程脚本跑完,天然就能判断执行结束时机,还能直接捕获stdout和stderr:

public function actionLauncher() {
    // 建立SSH连接
    $connection = ssh2_connect('server', 22);
    if (!$connection) {
        die('无法连接到远程服务器');
    }
    
    // 密码认证
    if (!ssh2_auth_password($connection, 'test', 'test')) {
        die('SSH认证失败');
    }

    // 执行远程脚本:不要加&后台执行,让PHP阻塞等待脚本完成
    $command = '. /home/test/counter.sh';
    $stream = ssh2_exec($connection, $command);
    if (!$stream) {
        die('无法执行远程命令');
    }

    // 开启阻塞模式,确保能读到完整输出
    stream_set_blocking($stream, true);

    // 获取标准输出(stdout)
    $stdout = stream_get_contents($stream);
    
    // 获取错误输出(stderr)
    $stderrStream = ssh2_fetch_stream($stream, SSH_STREAM_STDERR);
    stream_set_blocking($stderrStream, true);
    $stderr = stream_get_contents($stderrStream);

    // 关闭流资源
    fclose($stderrStream);
    fclose($stream);

    // 到这里,脚本已经100%执行完毕了
    echo "脚本执行完成!\n";
    echo "标准输出:\n{$stdout}\n";
    echo "错误输出:\n{$stderr}\n";

    // 如果需要把输出写入文件,这里可以自行处理本地或远程文件
    // file_put_contents('local_out.txt', $stdout);
}

方案2:后台执行脚本(异步,监控进程状态)

如果必须后台执行脚本(比如脚本耗时极长,不想阻塞PHP进程),那你需要先获取远程脚本的进程ID(PID),然后定期远程检查这个PID是否存活,以此判断脚本是否结束,同时读取你预先重定向的输出文件:

public function actionLauncher() {
    $connection = ssh2_connect('server', 22);
    ssh2_auth_password($connection, 'test', 'test');

    // 执行脚本时,把PID写入临时文件,同时重定向输出和错误到指定文件
    $command = '. /home/test/counter.sh > out2.txt 2>&1 & echo $! > /tmp/counter_pid.txt';
    $stream = ssh2_exec($connection, $command);
    stream_set_blocking($stream, true);
    fclose($stream);

    // 获取远程脚本的PID
    $pidStream = ssh2_exec($connection, 'cat /tmp/counter_pid.txt');
    stream_set_blocking($pidStream, true);
    $pid = trim(stream_get_contents($pidStream));
    fclose($pidStream);

    // 循环检查PID是否存活(可根据需求调整检查间隔)
    $isRunning = true;
    while ($isRunning) {
        // 远程执行ps命令,通过退出码判断进程是否存在
        $checkStream = ssh2_exec($connection, "ps -p {$pid} > /dev/null 2>&1; echo $?");
        stream_set_blocking($checkStream, true);
        $exitCode = trim(stream_get_contents($checkStream));
        fclose($checkStream);

        // 退出码为0表示进程还在运行,非0则已结束
        $isRunning = ($exitCode == 0);
        if ($isRunning) {
            sleep(2); // 等待2秒后再次检查
        }
    }

    // 脚本执行完毕,读取输出文件内容
    $outputStream = ssh2_exec($connection, 'cat out2.txt');
    stream_set_blocking($outputStream, true);
    $fullOutput = stream_get_contents($outputStream);
    fclose($outputStream);

    echo "脚本执行完成!完整输出:\n{$fullOutput}";

    // 清理临时PID文件
    ssh2_exec($connection, 'rm /tmp/counter_pid.txt');
}

关键注意点

  • 不要混用后台执行和阻塞流:你原来的代码加了&后台执行,又设置stream_set_blocking(true),这时候流会立刻读到EOF——因为后台命令已经脱离当前会话了,根本等不到脚本结束。
  • stderr捕获的正确姿势:必须通过ssh2_fetch_stream($stream, SSH_STREAM_STDERR)获取,注意要在stdout的流操作完成前(或同时)处理,确保流处于打开状态。
  • 权限问题:确保远程用户test对脚本、输出目录、临时PID目录有读写权限,不然会出现无法执行、无法写入文件的问题。

内容的提问来源于stack exchange,提问作者user3347598

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 04:13:47