How to write in Java to stdin of ssh?

2019-02-17 14:51发布

问题:

Everything works fine on the command line, but when I translate what I want into Java, the receiving process never gets anything on stdin.

Here's what I have:

private void deployWarFile(File warFile, String instanceId) throws IOException, InterruptedException {
    Runtime runtime = Runtime.getRuntime();
    // FIXME(nyap): Use Jsch.
    Process deployWarFile = runtime.exec(new String[]{
            "ssh",
            "gateway",
            "/path/to/count-the-bytes"});

    OutputStream deployWarFileStdin = deployWarFile.getOutputStream();
    InputStream deployWarFileStdout = new BufferedInputStream(deployWarFile.getInputStream());
    InputStream warFileInputStream = new FileInputStream(warFile);

    IOUtils.copy(warFileInputStream, deployWarFileStdin);
    IOUtils.copy(deployWarFileStdout, System.out);

    warFileInputStream.close();
    deployWarFileStdout.close();
    deployWarFileStdin.close();

    int status = deployWarFile.waitFor();
    System.out.println("************ Deployed with status " + status + " file handles. ************");
}

The script 'count-the-bytes' is simply:

#!/bin/bash

echo "************ counting stdin bytes ************"
wc -c
echo "************ counted stdin bytes ************"

The output indicates that the function hangs at the 'wc -c' line -- it never gets to the 'counted stdin bytes' line.

What's going on? Would using Jsch help?

回答1:

You might try closing the output stream before you expect wc -c to return.

IOUtils.copy(warFileInputStream, deployWarFileStdin);
deployWarFileStdin.close();
IOUtils.copy(deployWarFileStdout, System.out);

warFileInputStream.close();
deployWarFileStdout.close();


回答2:

Would using Jsch help?

Using JSch would only help if you would be using the setInputStream() and setOutputStream() methods of the channel instead of the IOUtils.copy method, since they manage the copying on a separate thread.

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

deployWarFile.setCommand("/path/to/count-the-bytes");

deployWarFile.setOutputStream(System.out);
deployWarFile.setInputStream(new BufferedInputStream(new FileInputStream(warFile)));

deployWarFile.connect();

(Here you somehow have to wait until the other side closes the channel.)

If you simply replaced the Runtime.exec with opening an ChannelExec (and starting it after getting the streams), the problem would be completely the same, and could be solved by the same solution mentioned by antlersoft, i.e. closing the input before reading the output:

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

deployWarFile.setCommand("/path/to/count-the-bytes");

OutputStream deployWarFileStdin = deployWarFile.getOutputStream();
InputStream deployWarFileStdout = new BufferedInputStream(deployWarFile.getInputStream());
InputStream warFileInputStream = new FileInputStream(warFile);

deployWarFile.connect();

IOUtils.copy(warFileInputStream, deployWarFileStdin);
deployWarFileStdin.close();
warFileInputStream.close();

IOUtils.copy(deployWarFileStdout, System.out);
deployWarFileStdout.close();

(Of course, if you have longer output, you will want to do input and output in parallel, or simply use the first method.)



回答3:

You probably get an error, but the process hangs because you are not reading the error stream. Taken from the Process JavaDoc

All its standard io (i.e. stdin, stdout, stderr) operations will be redirected to the parent process through three streams (Process.getOutputStream(), Process.getInputStream(), Process.getErrorStream()). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

So you need to read all of them. Using the ProcessBuilder is probably easier