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

Java通过SSH远程执行Linux脚本:如何判断执行是否正确成功

问题根因

你当前的异常是命令构造逻辑和错误流处理的双重问题导致的:

  • 你构造的命令为 脚本路径 > /dev/null 2>&1 & \n echo $!,将脚本丢到后台执行后,父shell不会等待脚本执行/启动结果,会立刻执行echo $!输出PID并退出,父shell退出码永远为0,脚本不存在、无权限、启动错误等异常都会被后台进程吞掉,外层拿不到错误信息
  • 代码中((ChannelExec) channel).setErrStream(System.err);将远程错误输出重定向到了本地控制台,你的业务逻辑读不到远程返回的错误内容,自然无法识别异常
解决方案

1. 改造Shell命令结构

调整命令逻辑,先做前置校验,捕获启动阶段异常,再输出PID,参考命令构造:

String scriptPath = "./export/home/therock/test/hello.bsh";
// 命令逻辑:先判断文件是否存在、是否有执行权限,校验不通过直接输出错误返回非0退出码,校验通过再后台启动输出PID
String command = String.format("if [ ! -f \"%s\" ]; then echo \"ERROR: 脚本不存在\" >&2; exit 1; fi; if [ ! -x \"%s\" ]; then echo \"ERROR: 脚本无执行权限\" >&2; exit 1; fi; %s > /dev/null 2>&1 & echo $!", scriptPath, scriptPath, scriptPath);

如果需要覆盖脚本启动瞬间的语法错误、解释器不存在等异常,可以进一步调整命令,将后台启动的错误信息临时输出到当前shell的错误流:

{ %s > /dev/null 2>&1 & echo PID:$! ; } 2>&1

返回内容如果以PID:开头则为启动成功,提取后面的数字作为PID即可,其余内容均为错误信息。

2. 修正Java代码错误流处理

删除错误流重定向到本地System.err的代码:

// 删掉这行
// ((ChannelExec) channel).setErrStream(System.err);

3. 调整结果判断逻辑

  • 如果channel返回的exitCode非0,直接判定为执行失败
  • 读取到的返回内容如果包含ERROR关键字或者非纯数字,判定为启动失败,抛出对应异常
  • 只有返回内容为纯数字/符合PID格式,才判定为启动成功返回PID
改造后核心代码示例
JSch jsch = new JSch();
String post_process_host="123.123.123.123";
Integer post_process_port=22;
String post_process_user="therock";
String post_process_pwd="XxXdfG$L";
String scriptPath = "./export/home/therock/test/hello.bsh";
// 构造校验型命令
String command = String.format("if [ ! -f \"%s\" ]; then echo \"ERROR: 脚本不存在\" >&2; exit 1; fi; if [ ! -x \"%s\" ]; then echo \"ERROR: 脚本无执行权限\" >&2; exit 1; fi; %s > /dev/null 2>&1 & echo $!", scriptPath, scriptPath, scriptPath);

Session session = jsch.getSession(post_process_user, post_process_host, post_process_port.intValue());
UserInfo ui = new SUserInfo(post_process_pwd, null);
session.setUserInfo(ui);
session.setPassword(post_process_pwd);
// 跳过HostKey校验,避免首次连接异常
session.setConfig("StrictHostKeyChecking", "no");
session.connect();

int timeout=10;
StringBuffer text = new StringBuffer();
int exitCode = -1;
String pid = null;
try {
    logger.info("Execution command: " + command);
    ChannelExec channel = (ChannelExec) session.openChannel("exec");
    channel.setCommand(command);
    channel.setInputStream(null);
    InputStream in = channel.getInputStream();
    InputStream err = channel.getErrStream();
    channel.connect(10000);
    byte[] tmp = new byte[1024];
    while (true) {
        timeout--;
        while (in.available() > 0) {
            int i = in.read(tmp, 0, 1024);
            if (i < 0) break;
            text.append(new String(tmp, 0, i, StandardCharsets.UTF_8));
        }
        while (err.available() > 0) {
            int i = err.read(tmp, 0, 1024);
            if (i < 0) break;
            text.append(new String(tmp, 0, i, StandardCharsets.UTF_8));
        }
        if (channel.isClosed()) {
            if (in.available() > 0 || err.available() > 0) continue;
            exitCode = channel.getExitStatus();
            logger.info("命令执行退出码: " + exitCode);
            logger.debug("命令输出: "+text);
            break;
        }
        if(timeout <=0) {
            throw new Exception("命令执行超时");
        }
        Thread.sleep(1000);
    }
    channel.disconnect();
    // 结果判断
    if(exitCode !=0) {
        throw new Exception("命令执行失败,退出码:"+exitCode+",错误信息:"+text);
    }
    String result = text.toString().trim();
    if(result.contains("ERROR")) {
        throw new Exception("脚本启动失败:"+result);
    }
    // 校验是否为纯数字PID
    if(!result.matches("\\d+")) {
        throw new Exception("启动命令返回异常,未获取到有效PID,返回内容:"+result);
    }
    pid = result;
    logger.info("脚本启动成功,PID:"+pid);
} catch (Exception e) {
    throw new RuntimeException("SSH执行失败", e);
} finally {
    if(session != null && session.isConnected()) {
        session.disconnect();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 23:15:04