ProcessBuilder: пересылка stdout и stderr запущенных процессов без блокировки основного streamа

Я создаю процесс на Java с помощью ProcessBuilder следующим образом:

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

Теперь моя проблема заключается в следующем: я хотел бы захватить все, что происходит через stdout и / или stderr этого процесса, и перенаправить его на System.out асинхронно. Я хочу, чтобы процесс и его redirect вывода выполнялись в фоновом режиме. Пока единственный способ, которым я нашел это, – вручную создать новый stream, который будет непрерывно читать из 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(); 

Хотя этот подход работает, он чувствует себя немного грязным. И, кроме того, он дает мне еще один stream для правильного управления и завершения. Есть ли лучший способ сделать это?

Только в Java 6 или более ранней StreamGobbler с так называемым 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 см. Ответ Евгения Дорофеева.

Используйте ProcessBuilder.inheritIO , он устанавливает источник и назначение для стандартного ввода-вывода subprocessа так же, как и для текущего процесса 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 лямбдой, которое позволяет вам предоставлять Consumer который будет обрабатывать вывод (например, журнал) по строкам. run() – однострочный, без проверенных исключений. Альтернативно для реализации 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(); } 

Здесь выходной stream перенаправляется на System.out и stream ошибок регистрируется на уровне ошибки logger .

Это так просто:

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

по .redirectErrorStream (true) вы сообщаете процессу объединить stream ошибок и вывода, а затем .redirectOutput (файл), вы перенаправляете объединенный вывод в файл.

Обновить:

Мне удалось это сделать следующим образом:

 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 } 

Теперь вы можете видеть оба выхода из основного и асинхронного streamов в System.out

Я тоже могу использовать только Java 6. Я использовал реализацию streamового сканера @ EvgeniyDorofeev. В моем коде, после завершения процесса, я должен немедленно выполнить два других процесса, каждый из которых сравнивает перенаправленный вывод (модульный тест на основе diff для обеспечения того, чтобы stdout и stderr были такими же, как и блаженные).

Потоки сканера не заканчиваются достаточно скоро, даже если я ждуFor () процесс для завершения. Чтобы код работал правильно, я должен убедиться, что streamи соединяются после завершения процесса.

 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(); } }); } 
 Thread thread = new Thread(() -> { new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.UTF_8)) .lines().forEach(...); }); thread.start(); 

Ваш собственный код идет вместо ...

По умолчанию созданный subprocess не имеет собственного терминала или консоли. Все его стандартные операции ввода-вывода (т.е. stdin, stdout, stderr) будут перенаправлены на родительский процесс, к которым можно получить доступ через streamи, полученные с помощью методов getOutputStream (), getInputStream () и getErrorStream (). Родительский процесс использует эти streamи для подачи ввода и получения вывода из subprocessа. Поскольку некоторые собственные платформы обеспечивают ограниченный размер буфера для стандартных streamов ввода и вывода, неспособность оперативно записать входной stream или прочитать выходной stream subprocessа, может привести к блокировке subprocessа или даже к взаимоблокировке.

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

Давайте будем гением компьютера.