ProcessBuilder:转发已启动的进程的stdout和stderr,而不阻塞主线程
我正在使用ProcessBuilderbuild立一个Javastream程,如下所示:
ProcessBuilder pb = new ProcessBuilder() .command("somecommand", "arg1", "arg2") .redirectErrorStream(true); Process p = pb.start(); InputStream stdOut = p.getInputStream();
现在我的问题如下:我想捕获进程的stdout和/或stderr,并将其redirect到System.out
asynchronous。 我希望进程及其输出redirect在后台运行。 到目前为止,我发现要做到这一点的唯一方法是手动产生一个新的线程,它将不断从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();
虽然这种方法的作品,感觉有点肮脏。 最重要的是,它给了我更多的线程来正确pipe理和终止。 有没有更好的方法来做到这一点?
在Java 6或更早版本中,只能使用所谓的StreamGobbler
(开始创build):
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的回答。
使用ProcessBuilder.inheritIO
,它将subprocess标准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(); }
线程会在subprocess结束时自动死掉,因为src
会EOF。
Java 8 lambda的一个灵活的解决scheme,可以让你提供一个Consumer
,将逐行处理输出(例如,logging)。 run()
是一个没有检查exception的单行程式。 另外实施Runnable
,它可以扩展Thread
而不是其他答案build议。
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); } }
然后你可以像这样使用它:
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(); }
在这里,输出stream被redirect到System.out
并且logging器在错误级别上logger
错误stream。
这很简单,如下所示:
File logFile = new File(...); ProcessBuilder pb = new ProcessBuilder() .command("somecommand", "arg1", "arg2") processBuilder.redirectErrorStream(true); processBuilder.redirectOutput(logFile);
通过.redirectErrorStream(true)告诉进程合并错误和输出stream ,然后通过.redirectOutput(文件)将合并输出redirect到一个文件。
更新:
我设法做到这一点如下:
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线程的输出
我也可以只使用Java 6.我使用@ EvgeniyDorofeev的线程扫描程序实现。 在我的代码中,一个进程完成后,我必须立即执行两个其他进程,每个进程比较redirect的输出(一个基于差异的unit testing,以确保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(); } }); }
默认情况下,创build的子stream程没有自己的terminal或控制台。 所有的标准I / O操作(例如stdin,stdout,stderr)都会被redirect到父进程,通过使用getOutputStream(),getInputStream()和getErrorStream()方法获得的stream。 父进程使用这些stream将input提供给subprocess并从subprocess获取输出。 由于某些本地平台仅为标准input和输出stream提供有限的缓冲区大小,因此如果不及时写入inputstream或读取子stream程的输出stream,可能会导致子stream程阻塞甚至死锁。