我正在使用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