ProcessBuilder:转发stdout和stderr启动进程而不阻塞主线程

Lor*_*igs 88 java processbuilder

我正在使用ProcessBuilder在Java中构建一个进程,如下所示:

ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
        .redirectErrorStream(true);
Process p = pb.start();

InputStream stdOut = p.getInputStream();
Run Code Online (Sandbox Code Playgroud)

现在我的问题如下:我想捕获通过该进程的stdout和/或stderr的任何内容,并将其重定向到System.out异步.我希望进程及其输出重定向在后台运行.到目前为止,我发现这样做的唯一方法是手动生成一个新的线程,该线程将连续读取stdOut,然后调用适当的write()方法System.out.

new Thread(new Runnable(){
    public void run(){
        byte[] buffer = new byte[8192];
        int len = -1;
        while((len = stdOut.read(buffer)) > 0){
            System.out.write(buffer, 0, len);
        }
    }
}).start();
Run Code Online (Sandbox Code Playgroud)

虽然这种方法很有效,但感觉有点脏.最重要的是,它为我提供了一个正确管理和终止的线程.有没有更好的方法来做到这一点?

Evg*_*eev 134

使用ProcessBuilder.inheritIO它,它将子进程标准I/O的源和目标设置为与当前Java进程的源和目标相同.

Process p = new ProcessBuilder().inheritIO().command("command1").start();
Run Code Online (Sandbox Code Playgroud)

如果Java 7不是一个选项

public static void main(String[] args) throws Exception {
    Process p = Runtime.getRuntime().exec("cmd /c dir");
    inheritIO(p.getInputStream(), System.out);
    inheritIO(p.getErrorStream(), System.err);

}

private static void inheritIO(final InputStream src, final PrintStream dest) {
    new Thread(new Runnable() {
        public void run() {
            Scanner sc = new Scanner(src);
            while (sc.hasNextLine()) {
                dest.println(sc.nextLine());
            }
        }
    }).start();
}
Run Code Online (Sandbox Code Playgroud)

子进程完成后线程将自动死亡,因为srcEOF 会自动死亡.

  • `sc` 需要关闭吗? (3认同)

asg*_*oth 66

要在Java 6或更早版本中使用所谓的StreamGobbler(您开始创建):

StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");

// any output?
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");

// start gobblers
outputGobbler.start();
errorGobbler.start();
Run Code Online (Sandbox Code Playgroud)

...

private class StreamGobbler extends Thread {
    InputStream is;
    String type;

    private StreamGobbler(InputStream is, String type) {
        this.is = is;
        this.type = type;
    }

    @Override
    public void run() {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null)
                System.out.println(type + "> " + line);
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于Java 7,请参阅Evgeniy Dorofeev的回答.

  • 对于Java 6及更早版本,似乎这是唯一的解决方案.对于java 7及更高版本,请参阅有关ProcessBuilder.inheritIO()的其他答案. (7认同)
  • 从输入流结束的那一刻起,它也将结束。 (2认同)

Ada*_*lik 18

一个灵活的Java 8 lambda解决方案,允许您提供一个Consumer逐行处理输出(例如,记录它)的解决方案.run()是一个没有检查异常抛出的单线程.作为实施的替代Runnable,它可以Thread像其他答案所暗示的那样延伸.

class StreamGobbler implements Runnable {
    private InputStream inputStream;
    private Consumer<String> consumeInputLine;

    public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) {
        this.inputStream = inputStream;
        this.consumeInputLine = consumeInputLine;
    }

    public void run() {
        new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它,例如:

public void runProcessWithGobblers() throws IOException, InterruptedException {
    Process p = new ProcessBuilder("...").start();
    Logger logger = LoggerFactory.getLogger(getClass());

    StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println);
    StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), logger::error);

    new Thread(outputGobbler).start();
    new Thread(errorGobbler).start();
    p.waitFor();
}
Run Code Online (Sandbox Code Playgroud)

这里输出流被重定向到,System.out并且错误级别由错误级别记录logger.


nik*_*aos 12

这很简单如下:

    File logFile = new File(...);
    ProcessBuilder pb = new ProcessBuilder()
        .command("somecommand", "arg1", "arg2")
    processBuilder.redirectErrorStream(true);
    processBuilder.redirectOutput(logFile);
Run Code Online (Sandbox Code Playgroud)

通过.redirectErrorStream(true)告诉进程合并错误和输出流,然后通过.redirectOutput(file)将合并输出重定向到文件.

更新:

我确实设法做到如下:

public static void main(String[] args) {
    // Async part
    Runnable r = () -> {
        ProcessBuilder pb = new ProcessBuilder().command("...");
        // Merge System.err and System.out
        pb.redirectErrorStream(true);
        // Inherit System.out as redirect output stream
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        try {
            pb.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    };
    new Thread(r, "asyncOut").start();
    // here goes your main part
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以在System.out中看到main和asyncOut线程的两个输出


mry*_*yan 5

有一个库提供了更好的 ProcessBuilder,zt-exec。这个库可以完全满足您的要求甚至更多。

使用 zt-exec 而不是 ProcessBuilder 时,代码如下所示:

添加依赖:

<dependency>
  <groupId>org.zeroturnaround</groupId>
  <artifactId>zt-exec</artifactId>
  <version>1.11</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

代码:

new ProcessExecutor()
  .command("somecommand", "arg1", "arg2")
  .redirectOutput(System.out)
  .redirectError(System.err)
  .execute();
Run Code Online (Sandbox Code Playgroud)

该库的文档位于: https: //github.com/zeroturnaround/zt-exec/