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
相关产品推荐
相关产品推荐

