ProcessBuilder:在不阻塞主线程的情况下转发已启动进程的stdout和stderr

时间:2021-10-26 20:59:45

I'm building a process in Java using ProcessBuilder as follows:

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

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

InputStream stdOut = p.getInputStream();

Now my problem is the following: I would like to capture whatever is going through stdout and/or stderr of that process and redirect it to System.out asynchronously. I want the process and its output redirection to run in the background. So far, the only way I've found to do this is to manually spawn a new thread that will continuously read from stdOut and then call the appropriate write() method of System.out.

现在我的问题是:我想捕获通过stdout和/或stderr的进程,并将其重定向到System。异步。我希望进程及其输出重定向在后台运行。到目前为止,我发现的惟一方法是手动生成一个新的线程,该线程将不断地从stdOut读取,然后调用System.out的适当的write()方法。

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

While that approach kind of works, it feels a bit dirty. And on top of that, it gives me one more thread to manage and terminate correctly. Is there any better way to do this?

虽然这种方法很有效,但感觉有点脏。最重要的是,它给了我更多的线程来管理和终止。有更好的办法吗?

7 个解决方案

#1


57  

To only way in Java 6 or earlier is with a so called StreamGobbler (which you are started to create):

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

...

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

For Java 7, see Evgeniy Dorofeev's answer.

对于Java 7,请参见Evgeniy Dorofeev的答案。

#2


107  

Use ProcessBuilder.inheritIO, it sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.

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

Process p = new ProcessBuilder().inheritIO().command("command1").start();

If Java 7 is not an option

如果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();
}

Threads will die automatically when subprocess finishes, because src will EOF.

当子进程完成时,线程将自动死亡,因为src将会EOF。

#3


10  

A flexible solution with Java 8 lambda that lets you provide a Consumer that will process the output (eg. log it) line by line. run() is a one-liner with no checked exceptions thrown. Alternatively to implementing Runnable, it can extend Thread instead as other answers suggest.

一个带有Java 8 lambda的灵活解决方案,它允许您提供一个将处理输出的使用者(例如。记录它)逐行。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);
    }
}

You can then use it for example like this:

你可以这样使用它:

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

Here the output stream is redirected to System.out and the error stream is logged on the error level by the logger.

在这里,输出流被重定向到系统。日志记录器将错误流记录在错误级别上。

#4


6  

It's as simple as following:

简单如下:

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

by .redirectErrorStream(true) you tell process to merge error and output stream and then by .redirectOutput(file) you redirect merged output to a file.

通过. redirecterrorstream (true),您告诉process合并错误和输出流,然后通过. redirectoutput (file)将合并后的输出重定向到一个文件。

Update:

更新:

I did manage to do this as follows:

我确实做到了以下几点:

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
}

Now you're able to see both outputs from main and asyncOut threads in System.out

现在您可以在System.out中看到主线程和异步线程的输出

#5


2  

I too can use only Java 6. I used @EvgeniyDorofeev's thread scanner implementation. In my code, after a process finishes, I have to immediately execute two other processes that each compare the redirected output (a diff-based unit test to ensure stdout and stderr are the same as the blessed ones).

我也只能使用Java 6。我使用了@EvgeniyDorofeev的线程扫描器实现。在我的代码中,在一个过程完成后,我必须立即执行两个其他的进程,每个进程都比较重定向输出(一个基于扩散的单元测试,以确保stdout和stderr与有福的输出相同)。

The scanner threads don't finish soon enough, even if I waitFor() the process to complete. To make the code work correctly, I have to make sure the threads are joined after the process finishes.

扫描器线程不会很快完成,即使我等待()进程完成。为了使代码正确工作,我必须确保在进程完成后将线程连接起来。

public static int runRedirect (String[] args, String stdout_redirect_to, String stderr_redirect_to) throws IOException, InterruptedException {
    ProcessBuilder b = new ProcessBuilder().command(args);
    Process p = b.start();
    Thread ot = null;
    PrintStream out = null;
    if (stdout_redirect_to != null) {
        out = new PrintStream(new BufferedOutputStream(new FileOutputStream(stdout_redirect_to)));
        ot = inheritIO(p.getInputStream(), out);
        ot.start();
    }
    Thread et = null;
    PrintStream err = null;
    if (stderr_redirect_to != null) {
        err = new PrintStream(new BufferedOutputStream(new FileOutputStream(stderr_redirect_to)));
        et = inheritIO(p.getErrorStream(), err);
        et.start();
    }
    p.waitFor();    // ensure the process finishes before proceeding
    if (ot != null)
        ot.join();  // ensure the thread finishes before proceeding
    if (et != null)
        et.join();  // ensure the thread finishes before proceeding
    int rc = p.exitValue();
    return rc;
}

private static Thread inheritIO (final InputStream src, final PrintStream dest) {
    return new Thread(new Runnable() {
        public void run() {
            Scanner sc = new Scanner(src);
            while (sc.hasNextLine())
                dest.println(sc.nextLine());
            dest.flush();
        }
    });
}

#6


0  

Thread thread = new Thread(() -> {
      new BufferedReader(
          new InputStreamReader(inputStream, 
                                StandardCharsets.UTF_8))
              .lines().forEach(...);
    });
    thread.start();

Your custom code goes instead of the ...

你的自定义代码去代替…

#7


-2  

By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and 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, or even deadlock.

默认情况下,创建的子进程没有自己的终端或控制台。它的所有标准I/O(即stdin、stdout、stderr)操作都将被重定向到父进程,在那里,可以通过使用getOutputStream()、getInputStream()和getErrorStream()方法获得的流来访问它们。父进程使用这些流向子进程提供输入并从子进程获得输出。由于某些本机平台仅为标准输入流和输出流提供有限的缓冲区大小,如果不能及时地编写输入流或读取子进程的输出流,可能会导致子进程阻塞,甚至死锁。

https://www.securecoding.cert.org/confluence/display/java/FIO07-J.+Do+not+let+external+processes+block+on+IO+buffers

https://www.securecoding.cert.org/confluence/display/java/FIO07-J。+ +不+让外部+流程+块+ + + IO +缓冲区

#1


57  

To only way in Java 6 or earlier is with a so called StreamGobbler (which you are started to create):

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

...

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

For Java 7, see Evgeniy Dorofeev's answer.

对于Java 7,请参见Evgeniy Dorofeev的答案。

#2


107  

Use ProcessBuilder.inheritIO, it sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.

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

Process p = new ProcessBuilder().inheritIO().command("command1").start();

If Java 7 is not an option

如果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();
}

Threads will die automatically when subprocess finishes, because src will EOF.

当子进程完成时,线程将自动死亡,因为src将会EOF。

#3


10  

A flexible solution with Java 8 lambda that lets you provide a Consumer that will process the output (eg. log it) line by line. run() is a one-liner with no checked exceptions thrown. Alternatively to implementing Runnable, it can extend Thread instead as other answers suggest.

一个带有Java 8 lambda的灵活解决方案,它允许您提供一个将处理输出的使用者(例如。记录它)逐行。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);
    }
}

You can then use it for example like this:

你可以这样使用它:

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

Here the output stream is redirected to System.out and the error stream is logged on the error level by the logger.

在这里,输出流被重定向到系统。日志记录器将错误流记录在错误级别上。

#4


6  

It's as simple as following:

简单如下:

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

by .redirectErrorStream(true) you tell process to merge error and output stream and then by .redirectOutput(file) you redirect merged output to a file.

通过. redirecterrorstream (true),您告诉process合并错误和输出流,然后通过. redirectoutput (file)将合并后的输出重定向到一个文件。

Update:

更新:

I did manage to do this as follows:

我确实做到了以下几点:

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
}

Now you're able to see both outputs from main and asyncOut threads in System.out

现在您可以在System.out中看到主线程和异步线程的输出

#5


2  

I too can use only Java 6. I used @EvgeniyDorofeev's thread scanner implementation. In my code, after a process finishes, I have to immediately execute two other processes that each compare the redirected output (a diff-based unit test to ensure stdout and stderr are the same as the blessed ones).

我也只能使用Java 6。我使用了@EvgeniyDorofeev的线程扫描器实现。在我的代码中,在一个过程完成后,我必须立即执行两个其他的进程,每个进程都比较重定向输出(一个基于扩散的单元测试,以确保stdout和stderr与有福的输出相同)。

The scanner threads don't finish soon enough, even if I waitFor() the process to complete. To make the code work correctly, I have to make sure the threads are joined after the process finishes.

扫描器线程不会很快完成,即使我等待()进程完成。为了使代码正确工作,我必须确保在进程完成后将线程连接起来。

public static int runRedirect (String[] args, String stdout_redirect_to, String stderr_redirect_to) throws IOException, InterruptedException {
    ProcessBuilder b = new ProcessBuilder().command(args);
    Process p = b.start();
    Thread ot = null;
    PrintStream out = null;
    if (stdout_redirect_to != null) {
        out = new PrintStream(new BufferedOutputStream(new FileOutputStream(stdout_redirect_to)));
        ot = inheritIO(p.getInputStream(), out);
        ot.start();
    }
    Thread et = null;
    PrintStream err = null;
    if (stderr_redirect_to != null) {
        err = new PrintStream(new BufferedOutputStream(new FileOutputStream(stderr_redirect_to)));
        et = inheritIO(p.getErrorStream(), err);
        et.start();
    }
    p.waitFor();    // ensure the process finishes before proceeding
    if (ot != null)
        ot.join();  // ensure the thread finishes before proceeding
    if (et != null)
        et.join();  // ensure the thread finishes before proceeding
    int rc = p.exitValue();
    return rc;
}

private static Thread inheritIO (final InputStream src, final PrintStream dest) {
    return new Thread(new Runnable() {
        public void run() {
            Scanner sc = new Scanner(src);
            while (sc.hasNextLine())
                dest.println(sc.nextLine());
            dest.flush();
        }
    });
}

#6


0  

Thread thread = new Thread(() -> {
      new BufferedReader(
          new InputStreamReader(inputStream, 
                                StandardCharsets.UTF_8))
              .lines().forEach(...);
    });
    thread.start();

Your custom code goes instead of the ...

你的自定义代码去代替…

#7


-2  

By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and 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, or even deadlock.

默认情况下,创建的子进程没有自己的终端或控制台。它的所有标准I/O(即stdin、stdout、stderr)操作都将被重定向到父进程,在那里,可以通过使用getOutputStream()、getInputStream()和getErrorStream()方法获得的流来访问它们。父进程使用这些流向子进程提供输入并从子进程获得输出。由于某些本机平台仅为标准输入流和输出流提供有限的缓冲区大小,如果不能及时地编写输入流或读取子进程的输出流,可能会导致子进程阻塞,甚至死锁。

https://www.securecoding.cert.org/confluence/display/java/FIO07-J.+Do+not+let+external+processes+block+on+IO+buffers

https://www.securecoding.cert.org/confluence/display/java/FIO07-J。+ +不+让外部+流程+块+ + + IO +缓冲区