java版本streamgobbler_java - ProcessBuilder:转发已启动进程的stdout和stderr而不阻塞主线程...

本文介绍如何在Java中使用ProcessBuilder启动进程,并异步地将进程的stdout和stderr重定向到System.out,避免阻塞主线程。提供了多种实现方式,包括Java 6及以上的版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

ProcessBuilder pb = new ProcessBuilder()

.command("somecommand", "arg1", "arg2")

.redirectErrorStream(true);

Process p = pb.start();

InputStream stdOut = p.getInputStream();

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

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

7个解决方案

119 votes

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

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

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

}

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

Evgeniy Dorofeev answered 2019-05-12T07:22:35Z

58 votes

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

}

}

}

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

asgoth answered 2019-05-12T07:21:53Z

13 votes

一个灵活的Java 8 lambda解决方案,允许您提供System.out,它将逐行处理输出(例如,记录它)。 logger是一个单行程序,没有抛出任何检查异常。 作为实施Runnable的替代方案,它可以扩展Thread,而不是其他答案建议。

class StreamGobbler implements Runnable {

private InputStream inputStream;

private Consumer consumeInputLine;

public StreamGobbler(InputStream inputStream, Consumer consumeInputLine) {

this.inputStream = inputStream;

this.consumeInputLine = consumeInputLine;

}

public void run() {

new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);

}

}

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

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

}

此处输出流重定向到System.out,错误级别由logger记录在错误级别。

Adam Michalik answered 2019-05-12T07:23:18Z

8 votes

这很简单如下:

File logFile = new File(...);

ProcessBuilder pb = new ProcessBuilder()

.command("somecommand", "arg1", "arg2")

processBuilder.redirectErrorStream(true);

processBuilder.redirectOutput(logFile);

通过.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

}

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

nike.laos answered 2019-05-12T07:24:10Z

2 votes

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

即使我在waitFor()过程完成,扫描程序线程也不会很快完成。 为了使代码正常工作,我必须确保在进程完成后连接线程。

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

}

});

}

jeff6times7 answered 2019-05-12T07:24:45Z

0 votes

Thread thread = new Thread(() -> {

new BufferedReader(

new InputStreamReader(inputStream,

StandardCharsets.UTF_8))

.lines().forEach(...);

});

thread.start();

您的自定义代码而不是...

Maxim answered 2019-05-12T07:25:14Z

-2 votes

默认情况下,创建的子进程没有自己的终端或控制台。 它的所有标准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]

sonal kumar sinha answered 2019-05-12T07:25:53Z

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值