How to read JSch command output?

孤人 提交于 2019-11-27 04:08:22
Noel Yap

The hanging was actually due to some unbalanced quotes in the command.

For posterity, the final code (after dealing with some other issues) is:

public String call(String hostname, String[] argv) throws SubprocessException {
    StringBuffer result = new StringBuffer();

    Session session = null;
    ChannelExec channel = null;

    try {
        // TODO: Emit friendly error if ~/.ssh doesn't exist.
        String dotSshDir = System.getProperty("user.home") + "/.ssh";
        String userName = System.getProperty("user.name");

        // TODO: Emit friendly error if ~/.ssh/known_hosts doesn't exist.
        jSch.setKnownHosts(dotSshDir + "/known_hosts");
        // TODO: Emit friendly error if ~/.ssh/id_rsa doesn't exist.
        jSch.addIdentity(dotSshDir + "/id_rsa");

        session = jSch.getSession(userName, hostname, 22);
        session.connect();

        channel = (ChannelExec) session.openChannel("exec");
        channel.setCommand(Joiner.on(" ").join(argv));
        channel.setInputStream(null);
        InputStream stdout = channel.getInputStream();
        InputStream stderr = channel.getErrStream();
        channel.connect();

        waitForChannelClosed(channel);

        if (channel.getExitStatus() != 0) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stderr));
            readAll(bufferedReader, result);

            throw new Exception(result.toString());
        } else {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stdout));
            readAll(bufferedReader, result);
        }
    } catch (Exception e) {
        throw new SubprocessException(e);
    } finally {
        if (channel != null) {
            channel.disconnect();
        }

        if (session != null) {
            session.disconnect();
        }
    }

    return result.toString();
}

The accepted answer by @NoelYap is wrong.

One has to read the output continuously, while waiting for the command to finish. Otherwise, if the command produces enough output to fill in an output buffer, the command will hang, waiting for the buffer to be consumed, what never happens. So you get a deadlock.

The following example reads both stdout and stderr continuously, while monitoring a command status. It is based on the official JSch exec.java example (just adds a reading of stderr).

ChannelExec channel = (ChannelExec)session.openChannel("exec");

channel.setCommand(
    "for((i=1;i<=10000;i+=2)); do echo \"Long output - $i\"; done ; " +
    "echo error output >&2");
InputStream commandOutput = channel.getExtInputStream();

StringBuilder outputBuffer = new StringBuilder();
StringBuilder errorBuffer = new StringBuilder();

InputStream in = channel.getInputStream();
InputStream err = channel.getExtInputStream();

channel.connect();

byte[] tmp = new byte[1024];
while (true) {
    while (in.available() > 0) {
        int i = in.read(tmp, 0, 1024);
        if (i < 0) break;
        outputBuffer.append(new String(tmp, 0, i));
    }
    while (err.available() > 0) {
        int i = err.read(tmp, 0, 1024);
        if (i < 0) break;
        errorBuffer.append(new String(tmp, 0, i));
    }
    if (channel.isClosed()) {
        if ((in.available() > 0) || (err.available() > 0)) continue; 
        System.out.println("exit-status: " + channel.getExitStatus());
        break;
    }
    try { 
      Thread.sleep(1000);
    } catch (Exception ee) {
    }
}

System.out.println("output: " + outputBuffer.toString());
System.out.println("error: " + errorBuffer.toString());

channel.disconnect();

If you add while (!channel.isClosed()) {} after the channel.connect();, you will see that with a sufficiently large i in the shell for loop (10000 is enough with in my environment), the loop never finishes.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!