如何使用Java向ssh的标准输入(stdin)写入数据?

5

在命令行中一切正常,但是当我将想要传输的内容转换成Java时,接收过程在stdin上永远不会接收到任何内容。

以下是我的代码:

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. ************");
}

脚本“count-the-bytes”的作用非常简单:
#!/bin/bash

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

输出结果表明该函数在“wc -c”行处挂起——它从未到达“counted stdin bytes”行。
发生了什么?使用Jsch是否有帮助?

当您使用IOCopy时,会返回哪个值? - Snicolas
我修改了代码,使用了copyLarge(); 它返回30054046。 - Noel Yap
3个回答

4
您可以尝试在期望 wc -c 返回之前关闭输出流。
IOUtils.copy(warFileInputStream, deployWarFileStdin);
deployWarFileStdin.close();
IOUtils.copy(deployWarFileStdout, System.out);

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

我已经添加了从标准输出读取以便调试为什么原始脚本(它要ssh到另一台机器)无法工作。在从标准输出读取之前关闭标准输入后,函数似乎可以工作(尽管有时之前也可以工作)。从标准输出读取是否会强制脚本完成它正在做的事情?这与调用waitFor()有什么不同? - Noel Yap
@Noel:这里的问题在于copywc的组合:只有当输入被关闭时,wc才会开始输出任何内容,而copy将等待脚本产生一些输出(以及它的结尾)。你也可以将copy和关闭放到一个单独的线程中。 - Paŭlo Ebermann

1
“使用Jsch会有帮助吗?” 只有在您使用通道的“setInputStream()”和“setOutputStream()”方法而不是“IOUtils.copy”方法时,使用JSch才会有所帮助,因为它们可以在单独的线程上管理复制。
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();

(在这里,您必须等待另一端关闭通道。)

如果您只是用打开ChannelExec(并在获取流后启动它)替换了Runtime.exec,那么问题将完全相同,并且可以通过antlersoft提到的相同解决方案来解决,即在读取输出之前关闭输入:

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();

当然,如果您有更长的输出,您将希望并行进行输入和输出,或者仅使用第一种方法。


0

你可能会收到一个错误,但进程挂起是因为你没有读取error stream。引自Process JavaDoc

所有标准流(即stdin、stdout和stderr)的操作将通过三个流(Process.getOutputStream()、Process.getInputStream()和Process.getErrorStream())重定向到父进程。父进程使用这些流向子进程提供输入并获取输出。由于某些本地平台仅为标准输入和输出流提供有限的缓冲区大小,未能及时写入子进程的输入流或读取子进程的输出流可能会导致子进程阻塞甚至死锁。

所以你需要读取所有流。使用ProcessBuilder可能更容易


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接