Java - 重定向进程 I/O(管道)正在停滞

标签 java process java-8 pipe piping

<分区>

我正在尝试将一个进程的输出重定向到另一个进程的输入。即,管道。在 Windows DOS shell 中执行此操作时,它看起来像

C:\> 目录/s/b 。 | findstr 数据 $

但是我正在尝试在 Java 中使用这样的命令,到目前为止它看起来像:

    Stopwatch sw = Stopwatch.createStarted();
    ProcessBuilder source = new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".");
    ProcessBuilder target = new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$");
    source.directory(new File("C:/"));
    target.directory(source.directory());
    // I am messing with the below lines, nothing is working
    source.redirectInput(target.redirectInput());
    source.redirectOutput(ProcessBuilder.Redirect.PIPE);
    source.redirectOutput(target.redirectInput());
    source.redirectInput(target.redirectOutput());
    target.redirectOutput(source.redirectInput());

    Process pSource = source.start();
    Process pTarget = target.start();
    log.debug("Running {} | {}", source.command(), target.command());
    try (BufferedReader br = new BufferedReader(new InputStreamReader(pTarget.getInputStream()))) {

        String line;
        while ((line = br.readLine()) != null)
            log.debug("{}", line);
    } finally {
        log.debug("Ending process {} with exist code {} in time {}", target.command(),
                pTarget.destroyForcibly().exitValue(), sw);
    }

但我发现 readLine 上的代码停顿了,所以这里有些东西不起作用。如何正确使用 IO 重定向?

最佳答案

redirectInput() resp接受或返回的对象,redirectOutput()描述了某种策略;它们不代表实际 channel 。
因此,通过语句 source.redirectInput(target.redirectInput()),您只是指定两个进程应该具有相同的策略,而不是链接 channel 。

事实上,在 Java 8 中,直接链接两个进程的 channel 是不可能的。要达到类似的效果,最好的方法是启动一个后台线程,该线程将读取第一个进程的输出并将其写入第二个进程过程的输入:

static List<Process> doPipeJava8() throws IOException {
    Process pSource = new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".")
                    .redirectInput(ProcessBuilder.Redirect.INHERIT)
                    .redirectError(ProcessBuilder.Redirect.INHERIT)
                    .start();
    Process pTarget;
    try {
        pTarget     = new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$")
                    .redirectErrorStream(true)
                    .redirectOutput(ProcessBuilder.Redirect.INHERIT)
                    .start();
    } catch(Throwable t) {
        pSource.destroyForcibly();
        throw t;
    }
    new Thread(() -> {
        try(InputStream srcOut = pSource.getInputStream();
            OutputStream dstIn = pTarget.getOutputStream()) {
            byte[] buffer = new byte[1024];
            while(pSource.isAlive() && pTarget.isAlive()) {
                int r = srcOut.read(buffer);
                if(r > 0) dstIn.write(buffer, 0, r);
            }
        } catch(IOException ex) {}
    }).start();
    return Arrays.asList(pSource, pTarget);
}

这会将错误 channel 、第一个进程的输入 channel 和最后一个进程的输出 channel 配置为 INHERIT,因此它们将使用我们的启动进程的控制台。第一个进程的输出和第二个进程的输入保留在默认的PIPE,这意味着建立一个管道到我们的启动进程,所以我们有责任从一个管道到另一个管道。

该方法可以用作

List<Process> sub = doPipeJava8();
Process pSource = sub.get(0), pTarget = sub.get(1);
pSource.waitFor();
pTarget.waitFor();

如果我们从 pTarget 进程的构建器中删除 .redirectOutput(ProcessBuilder.Redirect.INHERIT),我们可以读取最终输出:

List<Process> sub = doPipeJava8();
Process pSource = sub.get(0), pTarget = sub.get(1);
List<String> result = new BufferedReader(new InputStreamReader(pTarget.getInputStream()))
    .lines().collect(Collectors.toList());

Java 9 是第一个支持在子进程之间建立管道的 Java 版本。它简化了解决方案

static List<Process> doPipeJava9() throws IOException {
    return ProcessBuilder.startPipeline(
        List.of(new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".")
                    .redirectInput(ProcessBuilder.Redirect.INHERIT)
                    .redirectError(ProcessBuilder.Redirect.INHERIT),
                new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$")
                    .redirectErrorStream(true)
                    .redirectOutput(ProcessBuilder.Redirect.INHERIT)) );
}

它与其他解决方案的作用相同¹;上面的示例配置为让第一个进程从控制台读取(如果需要),最后一个进程写入控制台。同样,如果我们从最后一个流程构建器中省略 .redirectOutput(ProcessBuilder.Redirect.INHERIT),我们可以读取最后一个流程的输出。

¹ 除了它会在可能的情况下使用系统的 native 管道功能

关于Java - 重定向进程 I/O(管道)正在停滞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49743956/

相关文章:

java - servlet不加载jsp

java - 可用的 d-bus java 实现

linux - linux命令行查看进程的命令行和环境变量的方法

java - 对象...对象[] 和格式

java - 使用 java8 检查映射行中的空值

java - 通过 Maven 运行 testng 测试时更改控制台输出

c++ - 如何使用 C++ 在 Linux 中检测进程何时终止?

c - 为什么数组不能通过使用 fork() 函数来工作?

java - API请求(GET调用)是否可以向客户端返回响应并启动后台任务来完成请求

java - 以编程方式将 .PSD 文件转换为 .JPEG/.PNG 文件