使用JSCH执行命令并读取终端输出的一些使用心得
使用Jsch执行命令,并读取终端输出
jsch
Jsch是java实现的一个SSH客户端。开发JSCH的公司是 jcraft:
JCraft成立于1998年3月,是一家致力于Java应用程序和Internet / Intranet服务的应用程序开发公司。
Jcraft的总裁兼首席执行官是Atsuhiko Yamanaka博士
在Yamanaka博士于1998年3月创立JCraft之前,他已经加入NEC公司两年了,从事软件的研究和开发。
Yamanaka博士拥有日本东北大学的信息科学硕士学位和博士学位。他还获得了东北大学信息科学学士学位。他的主题与计算机科学的数学基础有关,尤其是构造逻辑和功能编程语言的设计。
执行命令
public static String executeCommandWithAuth(String command, SubmitMachineInfo submitMachineInfo, ExecuteCommandACallable<String> buffer) { Session session = null; Channel channel = null; InputStream in = null; InputStream er = null; Watchdog watchdog = new Watchdog(120000);//2分钟超时 try { String user = submitMachineInfo.getUser(); String host = submitMachineInfo.getHost(); int remotePort = submitMachineInfo.getPort(); JSch jsch = new JSch(); session = jsch.getSession(user, host, remotePort); Properties prop = new Properties(); //File file = new File(SystemUtils.getUserHome() + "/.ssh/id_rsa"); //String knownHosts = SystemUtils.getUserHome() + "/.ssh/known_hosts".replace('/', File.separatorChar); //jsch.setKnownHosts(knownHosts) //jsch.addIdentity(file.getPath()) //prop.put("PreferredAuthentications", "publickey"); //prop.put("PreferredAuthentications", "password"); // prop.put("StrictHostKeyChecking", "no"); session.setConfig(prop); session.setPort(remotePort); session.connect(); channel = session.openChannel("exec"); ((ChannelExec) channel).setPty(false); ((ChannelExec) channel).setCommand(command); // get I/O streams in = channel.getInputStream(); er = ((ChannelExec) channel).getErrStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); BufferedReader errorReader = new BufferedReader(new InputStreamReader(er, StandardCharsets.UTF_8)); Thread thread = Thread.currentThread(); watchdog.addTimeoutObserver(w -> thread.interrupt()); channel.connect(); watchdog.start(); String buf; while ((buf = reader.readLine()) != null) { buffer.appendBuffer(buf); if (buffer.IamDone()) { break; } } String errbuf; while ((errbuf = errorReader.readLine()) != null) { buffer.appendBuffer(errbuf); if (buffer.IamDone()) { break; } } //两分钟超时,无论什么代码,永久运行下去并不是我们期望的结果, //加超时好处多多,至少能防止内存泄漏,也能符合我们的预期,程序结束,相关的命令也结束。 //如果程序是前台进程,不能break掉,那么可以使用nohup去启动,或者使用子shell,但外层我们的程序一定要能结束。 watchdog.stop(); channel.disconnect(); session.disconnect(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } if (er != null) { er.close(); } } catch (Exception e) { // } if (channel != null) { channel.disconnect(); } if (session != null) { session.disconnect(); } watchdog.stop(); } return buffer.endBuffer(); }
public interface ExecuteCommandACallable<T> { boolean IamDone();//提前结束执行,如果终端是无限输出,则可以在达到一定条件的时候,通过IamDone通知上述程序结束读取。 //for buffer ExecuteCommandACallable<T> appendBuffer(String content);//异步追加输出到自定义的Buffer String endBuffer();//正常结束Buffer, }
上述两段代码已经用于生产环境,如果通过异步的方式启动,可以在Buffer中通过appendBuffer方法接收每一行的输出。可以打印到终端,也可以写如文件,甚至写到websocket,Kafka等。
实际遇到的问题
就是执行一些命令,例如启动 spark,spark-submit
,启动 flink, flink run
,都无法读取终端输出,且都阻塞到readLine。
思路,既然我们读的是标准终端输出,以及错误终端输出,那么我们是见过