使用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。
思路,既然我们读的是标准终端输出,以及错误终端输出,那么我们是见过 2>&1这种重定向,是不是可以利用他重定向到我们的流呢?
经过实践,解决方案就是,无论执行什么命令,在后面都可以增加 2>&1,即便是 ls 2>&1, date 2>&1.
至于为什么不加 2>&1就不行,或许是因为以这种方式启动的命令输出,是到了 /dev/tty了,或者某个非ssh进程的pipe。
非充分验证
- 执行
executeCommandWithAuth("date;sleep 1m;date", submitMachineInfo, buffer);, 并查看Linux中通过ssh协议产生bash进程的fd - 执行
executeCommandWithAuth("date;sleep 1m;date 2>&1", submitMachineInfo, buffer);, 并查看Linux中通过ssh协议产生bash进程的fd
这两条命令都是可以在Java程序中打印出结果的。
例如:
2019年 12月 05日 星期四 11:57:16 CST
2019年 12月 05日 星期四 11:58:16 CST
Linux中的执行结果如下
[root@hm arvin]# ps aux | grep bash
arvin 7886 0.0 0.0 113248 1592 ? Ss 11:55 0:00 bash -c date;sleep 1m;date
root 7910 0.0 0.0 112668 972 pts/1 S+ 11:56 0:00 grep --color=auto bash
[root@hm arvin]# ll /proc/7886/fd
总用量 0
lr-x------ 1 arvin arvin 64 12月 5 11:55 0 -> pipe:[64748]
l-wx------ 1 arvin arvin 64 12月 5 11:55 1 -> pipe:[64749]
l-wx------ 1 arvin arvin 64 12月 5 11:55 2 -> pipe:[64750]
[root@hm arvin]# ps aux | grep bash
root 617 0.0 0.0 115248 944 ? S 09:40 0:00 /bin/bash /usr/sbin/ksmtuned
arvin 7968 0.0 0.0 113248 1588 ? Ss 11:57 0:00 bash -c date;sleep 1m;date 2>&1
root 8000 0.0 0.0 112672 972 pts/1 S+ 11:57 0:00 grep --color=auto bash
[root@hm arvin]# ll /proc/7968/fd
总用量 0
lr-x------ 1 arvin arvin 64 12月 5 11:57 0 -> pipe:[64278]
l-wx------ 1 arvin arvin 64 12月 5 11:57 1 -> pipe:[64279]
l-wx------ 1 arvin arvin 64 12月 5 11:57 2 -> pipe:[64280]
[root@hm arvin]#
可以看到这两种执行方式,之所以能在Java中打印出来输出结果,是因为打印到了某些pipe,根据推测是打印到了ssh进程的pipe,所以才能通过SSH协议送回我们本地机器Java应用程序中的jsch的线程内的。
这里我就不再制造不加重定向无法打印的例子,但可以用此方法验证,推测Linux进程输出到别的位置了。
使用JSCH执行命令并读取终端输出的一些使用心得的更多相关文章
- 执行命令行, 并获取输出字符(比如OSQL)
直接贴代码了, 没什么好说的, 很简单, 也不需要注释 function DoCMD(ACommand: AnsiString; var ACmdResult: string): Boolean; v ...
- Mac系统终端命令行不执行命令 总出现command not found解决方法
配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的 保存命令是: source .bas ...
- Linux 命令 - watch: 反复执行命令,全屏显示输出
watch 命令周期性地执行命令,全屏显示输出.可以通过 watch 命令反复执行某一程序来监视它的输出变化. 命令格式 watch [-dhvt] [-n <seconds>] [--d ...
- Python3执行DOS命令并截取其输出到一个列表字符串,同时写入一个文件
#执行DOS命令并截取其输出到一个列表字符串,同时写入一个文件#这个功能很有用listing=os.popen('ipconfig').readlines()for i in listing: pri ...
- bash执行命令分别输出正常日志和错误日志
0. 说明 执行bash命令的定时任务时候,希望能把正常的日志输出到一个文件里面,同时如果执行的过程发生异常则把异常日志输出到另一个不同的文件中.方便今后异常排查,极大有利于快速定位出错位置. 需要了 ...
- [转] Mac系统终端命令行不执行命令 总出现command not found解决方法
配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的 保存命令是: source .bash ...
- 分析RunTime执行命令以及得到返回值
RunTime执行命令得到返回值 我们有在好好几篇博客里提到过RunTime,比如 JAVA之旅(二十三)--System,RunTime,Date,Calendar,Math的数学运算 Androi ...
- NET Core 跨平台执行命令、脚本
一.前言 我们可能会遇到需要在程序中执行一些系统命令,来获取一些信息:或者调用shell脚本..NET Core 目前已经可以跨平台执行,那么它如何跨平台执行命令呢,请看下面的讲解. 二.Proces ...
- [转]python3之paramiko模块(基于ssh连接进行远程登录服务器执行命令和上传下载文件的功能)
转自:https://www.cnblogs.com/zhangxinqi/p/8372774.html 阅读目录 1.paramiko模块介绍 2.paramiko的使用方法 回到顶部 1.para ...
随机推荐
- 虚拟环境:virtualenv与virtualenvwrapper
前言: 在使用 Python 开发的过程中,工程一多,难免会碰到不同的工程依赖不同版本的库的问题: 亦或者是在开发过程中不想让物理环境里充斥各种各样的库,引发未来的依赖灾难. 此时,我们需要对于不同的 ...
- 如何用好redis pipeline
编者注:pipeline是Redis的一个提高吞吐量的机制,适用于多key读写场景,比如同时读取多个key的value,或者更新多个key的value.工作过程中发现挺多小伙伴都对pipeline多少 ...
- 前端上传图片 base64转二进制上传
var dataURItoBlob = function (dataURI) { var byteString = atob(dataURI.split(',')[1]); var mimeStrin ...
- Ajax自我总结
一念起.万水千山皆有情. 一念灭.沧海桑田已无心. ------ 随记 本文主要针对ajax原理介绍,很少涉及实例,主要用于对知识的梳理总结,方便以后学习和查询... Ajax 一.Ajax是 ...
- [考试反思]1026csp-s模拟测试89:不公
稍垃圾.因为T1没A. 赶巧前一段时间学了杜教筛,结果因为教练放错题. 然后考场上疯狂yy,最后水到了一个AC. 其实的确挺不公平的,不少人也没学呢. 如果只算T1和T3的分数的话,那70分就是个垃圾 ...
- Unix/Linux 从哪儿来?那些改变世界的人们...
昨天看文章时发现自己对 linux 操作系统不够了解,还记得 17 年时听过老师的一些课,对 linux 的历史有一点了解,不过当时并没有记录笔记,现在已经忘的差不多了. 这次从网上找资料,又重新看了 ...
- [转载]1.1 UiPath下载安装与激活
一.UiPath下载 1.打开官网https://www.uipath.com.cn,点击开始试用 2.选择获取UiPath社区版 3.填写姓.名.电子邮箱地址.专业领域.职位.公司.公司所在行业.国 ...
- Abp vNext 自定义 Ef Core 仓储引发异常
问题 在使用自定义 Ef Core 仓储和 ABP vNext 注入的默认仓储时,通过两个 Repository 进行 Join 操作,提示 Cannot use multiple DbContext ...
- day6-作业(不完整)
# 1.用代码实现:利用下划线将列表的'每一个元素'拼接成字符串 li=['ndfj','dlfj',12434]# 注意是将元素与元素转换为字符串之间用_拼接,而不是将每个字符串进行拼接 li=[' ...
- 一分钟带你学会利用mybatis-generator自动生成代码!
目录 一.MyBatis Generator简介 二.使用方式 三.实战 之前的文章<SpringBoot系列-整合Mybatis(XML配置方式)>介绍了XML配置方式整合的过程,本文介 ...