Linux cmd 执行后读取输出将进入无限循环

Reading output after Linux cmd execution is going to infinite loop

我正在从 jsch 执行命令,命令完成后我需要显示命令输出。在读取命令输出时需要很长时间,即使在执行命令后它的 ns 也不是来自 while 循环。以下是我的代码:

java.util.Properties config = new java.util.Properties(); 
        config.put("StrictHostKeyChecking", "no");


        JSch jsch = new JSch();
        session=jsch.getSession(user, host, 22);
        session.setConfig("PreferredAuthentications","publickey,keyboard-interactive,password");
        session.setPassword(password);

        session.setConfig(config);
        session.connect();
        System.out.println("Connected");

        channel=session.openChannel("shell");
        OutputStream ops = channel.getOutputStream();
        PrintStream ps = new PrintStream(ops, true);

         channel.connect();
         ps.println(cmd);
        InputStream in=channel.getInputStream();
        byte[] tmp=new byte[1024];
        while(channel.getExitStatus() == -1){
          while(in.available()>0){
            int i=in.read(tmp, 0, 1024);
            if(i<0)break;
            System.out.print(new String(tmp, 0, i));
          }
          if(channel.isClosed()){
            System.out.println("exit-status: "+channel.getExitStatus());
            break;
          }
          try{Thread.sleep(3000);}catch(Exception ee){}
        }

我在命令末尾添加了exit命令

据我目前观察到的情况,channel.isClosed() 只有在 SSH 守护程序向您发送了输出流(stdout 和 stderr)的所有内容后才为真。

如果这个内容很大,它不会完全适合 sshd 和您的进程之间的 TCP 连接的缓冲区,通道将永远保持打开状态。当您可能在 stdout 和 stderr 中都有很多内容时,问题会更糟,因为您不知道优先使用哪个流。对 read() 的调用是阻塞的,不可能超时。

我们使用的解决方案是在主线程中谨慎地同时使用 stdout 和 stderr 的所有内容,如果我们不确定它不会阻塞,则不要在任何流上调用 read()

        StringBuilder result = new StringBuilder();
        StringBuilder error = new StringBuilder();

        InputStream out = channel.getInputStream();
        InputStream err = channel.getErrStream();

        while(!channel.isClosed() || out.available() > 0 || err.available() > 0) {
            if (out.available() > 0) {
                size = out.read(cbuf);
                if (size > 0) {
                    result.append(new String(cbuf, 0, size));
                } 
                // normally the 'if' is useless as available() guarantee size > 0
                // it's just an extra-precaution
            } else if (err.available() > 0) {
                size = err.read(cbuf);
                if (size > 0) {
                    error.append(new String(cbuf, 0, size));
                }
            } else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }

当我从 UNIX 服务器读取 InputStream 时,我遇到了同样的问题。

out.available() 在 while 循环中读取字节后变为 0,同时我们应该注意到 channel.getExitStatus() 是 -1,表示失败。命令执行失败。

因此,我决定使用channel.getOutputStream()中的日志。下面是我创建的一个可重用方法,它有助于在远程服务器中执行命令并将 returns 日志作为字符串。我们可以操作包含服务器输出的日志数据,(有些地方可能不合适,请忽略)

public static String executeCommand(String command, int timeout) {
        try {
            Channel channel = getSession().openChannel("shell");
            setChannel(channel);

            PrintStream standard = System.out;
            OutputStream out = getChannel().getOutputStream();
            PrintStream commander = new PrintStream(out, true);
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream(baos);

            getChannel().setOutputStream(ps, true);
            getChannel().connect();

            for (String c : command.split(";")) {
                commander.println(c);
                try {Thread.sleep(timeout);}catch(Exception e) {}
            }

            ps.flush();
            commander.flush();
            
            System.setOut(standard);
            System.out.println(baos.toString());
            
            out.close();
            baos.close();
            
            System.out.println("Command executed successfully - "+command);
            unloadChannel();
            
            return baos.toString();

        }catch (JSchException e) {
            System.out.println("Channel connection failed");
            e.printStackTrace();
            return "";
            //throw new RuntimeException("Shell channel connection failed");
        }catch (IOException e) {
            System.out.println("IO Exception occurred");
            e.printStackTrace();
            return "";
        }

    }

确保关闭频道和流以避免泄漏...如果我有任何错误,请随时提供建议。

我知道这是一种解决方法,但没有任何其他可行的解决方案...