Domanda

Short Version: Is it possible to log the stdout and stderr on the local side of a command executed remotely via ssh in the same order as it was output on the remote host? If so, how?

Long Version:

I am attempting to log the standard and error output from a remotely exec'd SSH command (using Jsch) in the same order as it was output by the remote command. In other words, if the remote command writes "a" to stdout, then "b" to stderr, then "c" to stdout, I want the log on the client (local) side to read:

a
b
c

Below is what I have so far. It comes relatively close to what I want, but I think it will be apparent that it does not guarantee the correct output order on the client side.

public int exec(String strCommand) throws ExceptionUnableToExecCommand {
    JSch jsch = new JSch();
    Session session = null;
    ChannelExec channel = null;
    try {
          session = jsch.getSession(user, host, 22);
          UserInfo ui = new cyclOps.jsch.UserInfo(password);
          session.setUserInfo(ui);
          session.connect();
          channel = (ChannelExec) session.openChannel("exec");
          channel.setCommand(strCommand);
          channel.setInputStream(null);
          InputStream in = channel.getInputStream();
          InputStream err = channel.getErrStream();
          channel.connect();
          /* getOutput() defined below. */
          return this.getOutput(channel, in, err);
    } catch (JSchException | IOException e) {
        throw new ExceptionUnableToExecCommand("Unable to execute " + strCommand + " " + this.toString(), e);
    } finally {
        if (channel != null) channel.disconnect();
        if (session != null) session.disconnect();
    }
}

private int getOutput(ChannelExec channel, InputStream in, InputStream err) throws IOException { 
    byte[] tmp = new byte[1024];
    while(true){
        while(in.available() > 0){
            int i=in.read(tmp, 0, 1024);
            if(i<0)break;
            this.sshLogger.logOutputFromSSH(new String(tmp, 0, i));
        }
        while(err.available() > 0){
            int i=err.read(tmp, 0, 1024);
            if(i<0)break;
            this.sshLogger.logOutputFromSSH(new String(tmp, 0, i));
        }
        if(channel.isClosed()){
            return channel.getExitStatus();
        }
        try{Thread.sleep(1000);}catch(Exception ee){}
    }
}

I think I should point out that this is a modified version of Exec.java from the Jsch web site examples.

È stato utile?

Soluzione

How about the following chunk of code?

channel.setCommand(command);

PipedOutputStream pos=new PipedOutputStream();
PipedInputStream pis=new PipedInputStream(pos);
channel.setOutputStream(pos);
channel.setExtOutputStream(pos);
InputStream in=pis;

channel.connect();

Altri suggerimenti

You are working with separate streams, so no, there is no way you can possibly guarantee to get the exact same output.

that said, your code has a number of potential problems, among them:

  • InputStream.available is a generally useless method and should be avoided
  • you are reading bytes and converting arbitrary byte arrays to chars/Strings, which could result in broken output (if you are getting multi-byte chars you could end up splitting them).
  • reading 2 different blocking streams with a single thread can result in deadlock
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top