代码之家  ›  专栏  ›  技术社区  ›  E.S.

Java-重定向进程输入/输出(管道)暂停[重复]

  •  1
  • E.S.  · 技术社区  · 6 年前

    我试图将一个进程的输出重定向到另一个进程的输入。一、 e.管道。在Windows DOS shell中执行此操作时

    C:\> dir /s/b . | findstr dat$

    然而,我尝试在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重定向?

    1 回复  |  直到 6 年前
        1
  •  4
  •   Holger    6 年前

    接受或返回的对象 redirectInput() resp, redirectOutput() 描述某项政策;它们不代表实际的通道。
    所以根据声明 source.redirectInput(target.redirectInput()) 您只是指定两个进程应该具有相同的策略,而不是链接通道。

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

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

    这将错误通道、第一个进程的输入通道和最后一个进程的输出通道配置为 INHERIT 因此,他们将使用我们的启动过程控制台。第一个过程输出和第二个过程输入保持默认值 PIPE 这意味着建立一个管道 我们的启动过程 ,因此我们有责任将数据从一个管道复制到另一个管道。

    该方法可用作

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

    如果我们删除 .redirectOutput(ProcessBuilder.Redirect.INHERIT) 来自 pTarget 过程中,我们可以读取最终输出:

    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) 从最后一个process builder中,我们可以读取最后一个流程输出。

    但如果可能,它将使用系统本地管道功能