使用JSCH执行命令并读取终端输出的一些使用心得

使用Jsch执行命令,并读取终端输出

jsch

http://www.jcraft.com/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。

非充分验证

  1. 执行 executeCommandWithAuth("date;sleep 1m;date", submitMachineInfo, buffer);, 并查看Linux中通过ssh协议产生bash进程的fd
  2. 执行 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中的执行结果如下

[[email protected] 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
[[email protected] 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]
[[email protected] 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
[[email protected] 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]
[[email protected] arvin]#

可以看到这两种执行方式,之所以能在Java中打印出来输出结果,是因为打印到了某些pipe,根据推测是打印到了ssh进程的pipe,所以才能通过SSH协议送回我们本地机器Java应用程序中的jsch的线程内的。
这里我就不再制造不加重定向无法打印的例子,但可以用此方法验证,推测Linux进程输出到别的位置了。

原文地址:https://www.cnblogs.com/slankka/p/11988477.html

时间: 2024-10-16 20:15:06

使用JSCH执行命令并读取终端输出的一些使用心得的相关文章

inux学习笔记三 后台执行命令

1.cron 系统调度进程.是SHELL一个LUNX下的定时执行工具,在无需人工干预下进行作业. $/sbin/service crond start --启动crontab服务 $/sbin/service crond stop --停止crontab服务 $/sbin/service crond restart --重新启动服务 $/sbin/service crond reload --重新加载配置 linux中,默认不会开启cron服务,在启动cron服务后,才能享受该服务. cron

Gnome Terminal,Xshell等终端模拟器中执行命令出现乱码问题解决

一.前言 Xshell跟Gnome Terminal相比,两者都是终端模拟器(在Xshell中也可以执行简单的内置命令,如"cd","ls"等),地位相同. 二.原理分析 涉及到乱码,那么需要了解编码解码过程.在终端模拟器中执行命令,通信过程示意图如图1所示. 图1 在以上通信过程中,在"命令执行单元"处发生了一系列的编码解码过程,在"终端模拟器"处也发生了一系列的编码解码过程,此外,我们常常创建SSH连接,从而建立一个远端S

Mac系统终端命令行不执行命令 总出现command not found解决方法

配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的     保存命令是:  source .bash_profile 说下我的解决方法: 1,在命令行中输入: export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin 这样可以保证命令行命令暂时可以使用.命令执行完之后先不要关闭终端. 2,输入 cd ~/ 进入当前用户的home目录. 3,

Linux 命令 - watch: 反复执行命令,全屏显示输出

watch 命令周期性地执行命令,全屏显示输出.可以通过 watch 命令反复执行某一程序来监视它的输出变化. 命令格式 watch [-dhvt] [-n <seconds>] [--differences[=cumulative]] [--help] [--interval=<seconds>] [--no-title] [--version] <command> 命令参数 -n, --interval 指定间隔时间.默认情况下,watch 每隔 2 秒执行一次命令

[转] Mac系统终端命令行不执行命令 总出现command not found解决方法

配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的     保存命令是: source .bash_profile 说下我的解决方法: 1,在命令行中输入: [cpp] view plain copy print? export PATH=/usr/bin:/usr/sbin:/bin:/sbin:/usr/X11R6/bin 这样可以保证命令行命令暂时可以使用.命令执行完之后先不要关闭终端.

Linux命令备忘实例(1)——终端输出

终端是用户与shell环境进行交互的窗口,所有命令的交互结果大部分都是从终端直接显示给用户,因此这部分是友好显示结果的基础. 1.命令概览 echo是基本的终端输出命令,直接将传入的参数输入,命令格式如下: echo [options] toBeOutput 详细说明如下: 2.特性与实例说明 1.换行符 默认情况下会在每次调用之后添加一个换行符.使用-n选项可以消除这个默认值. [email protected]:~$ echo test a line [email protected]:~$

xshell等终端后台执行命令

在xshell或者其他终端软件执行命令,使用nohup commond & 关闭xshell窗口的时候命令照样被关闭了,这种情况可以使用命令screen,或者tmux命令 screen,tmux的功能很强大,可以进入新的bash,打开新的窗口,还可以分屏等等 也可以使用screen或者tmux 加命令,直接在screen或者tmux开启的shell中执行命令.

java使用jsch远程链接linux执行命令

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader; import com.jcraft.jsch.Channel;import com.jcraft.jsch.ChannelExec;import com.jcraft.jsch.JSch;import com.jcraft.jsch.JSchException;i

【shell】-【批量远程MySQL,执行命令】-【工作总结】

昨天下班前,老板给了一批LOG数据库IP地址,需要统计LOG表里Message字段top 10的结果,并输出到一个excel文件里.抽查看了下,有两种格式的以当天日期结尾的表名.由于数量太多,时间紧迫,只好写批量脚本解决问题.并以此扩展,解析其中的几个常用shell程序,主体脚本写在文章后半部分.学习shell重在灵活运用命令,形成自己的思维方式,和书写习惯,脚本参考即可. 解题过程步骤:1.梳理IP地址及对应表名2.确定查询SQL3.批量查询数据 完整脚本附在文章最后 解析下常用到的知识点: