Я создаю процесс на Java с помощью ProcessBuilder следующим образом:
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
.redirectErrorStream(true);
Process p = pb.start();
InputStream stdOut = p.getInputStream();
Теперь моя проблема заключается в следующем: я хотел бы захватить все, что проходит через stdout и / или stderr этого процесса, и перенаправить его System.out
асинхронно. Я хочу, чтобы процесс и его перенаправление вывода выполнялись в фоновом режиме. До сих пор я нашел единственный способ сделать это - вручную создать новый поток, который будет непрерывно читать, 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();
Хотя такой подход работает, он кажется немного грязным. И вдобавок ко всему, это дает мне еще один поток для правильного управления и завершения. Есть ли лучший способ сделать это?
java
processbuilder
LordOfThePigs
источник
источник
org.apache.commons.io.IOUtils.copy(new ProcessBuilder().command(commandLine) .redirectErrorStream(true).start().getInputStream(), System.out);
Ответы:
Единственный способ в 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 см. Ответ Евгения Дорофеева.
источник
При использовании
ProcessBuilder.inheritIO
он устанавливает источник и назначение для стандартного ввода-вывода подпроцесса такими же, как и у текущего процесса 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.источник
inheritIO()
одним из этих удобныхredirect*(ProcessBuilder.Redirect)
методов в следующий раз, когда мне понадобится сделать это в проекте java 7. К сожалению, мой проект - java 6.sc
нужно закрыть?Гибкое решение с лямбда-
Consumer
выражением Java 8, которое позволяет вам предоставить объект, который будет обрабатывать вывод (например, регистрировать его) построчно.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); } }
Затем вы можете использовать его, например, так:
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
.источник
forEach()
вrun()
методе будет блокироваться до тех пор, пока поток не откроется, ожидая следующей строки. Он выйдет, когда поток будет закрыт.Это очень просто:
File logFile = new File(...); ProcessBuilder pb = new ProcessBuilder() .command("somecommand", "arg1", "arg2") processBuilder.redirectErrorStream(true); processBuilder.redirectOutput(logFile);
с помощью .redirectErrorStream (true) вы сообщаете процессу объединить ошибку и выходной поток, а затем с помощью .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 }
Теперь вы можете видеть оба вывода из основного и asyncOut потоков в System.out
источник
Простое решение java8 с захватом обоих выходов и реактивной обработкой с использованием
CompletableFuture
:static CompletableFuture<String> readOutStream(InputStream is) { return CompletableFuture.supplyAsync(() -> { try ( InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); ){ StringBuilder res = new StringBuilder(); String inputLine; while ((inputLine = br.readLine()) != null) { res.append(inputLine).append(System.lineSeparator()); } return res.toString(); } catch (Throwable e) { throw new RuntimeException("problem with executing program", e); } }); }
И использование:
Process p = Runtime.getRuntime().exec(cmd); CompletableFuture<String> soutFut = readOutStream(p.getInputStream()); CompletableFuture<String> serrFut = readOutStream(p.getErrorStream()); CompletableFuture<String> resultFut = soutFut.thenCombine(serrFut, (stdout, stderr) -> { // print to current stderr the stderr of process and return the stdout System.err.println(stderr); return stdout; }); // get stdout once ready, blocking String result = resultFut.get();
источник
Есть библиотека, которая предоставляет лучший ProcessBuilder, zt-exec. Эта библиотека может делать именно то, о чем вы просите, и даже больше.
Вот как будет выглядеть ваш код с zt-exec вместо ProcessBuilder:
добавить зависимость:
<dependency> <groupId>org.zeroturnaround</groupId> <artifactId>zt-exec</artifactId> <version>1.11</version> </dependency>
Код :
new ProcessExecutor() .command("somecommand", "arg1", "arg2") .redirectOutput(System.out) .redirectError(System.err) .execute();
Документация библиотеки находится здесь: https://github.com/zeroturnaround/zt-exec/
источник
Я тоже могу использовать только Java 6. Я использовал реализацию сканера потоков @ EvgeniyDorofeev. В моем коде после завершения процесса я должен немедленно выполнить два других процесса, каждый из которых сравнивает перенаправленный вывод (модульный тест на основе diff, чтобы убедиться, что stdout и stderr такие же, как и благословенные).
Потоки сканера не завершаются достаточно быстро, даже если я жду завершения процесса (). Чтобы код работал правильно, я должен убедиться, что потоки соединяются после завершения процесса.
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(); } }); }
источник
В дополнение к ответу msangel я хотел бы добавить следующий блок кода:
private static CompletableFuture<Boolean> redirectToLogger(final InputStream inputStream, final Consumer<String> logLineConsumer) { return CompletableFuture.supplyAsync(() -> { try ( InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); ) { String line = null; while((line = bufferedReader.readLine()) != null) { logLineConsumer.accept(line); } return true; } catch (IOException e) { return false; } }); }
Он позволяет перенаправить входной поток (stdout, stderr) процесса другому потребителю. Это может быть System.out :: println или что-то еще, использующее строки.
Применение:
источник
Thread thread = new Thread(() -> { new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.UTF_8)) .lines().forEach(...); }); thread.start();
Ваш собственный код используется вместо
...
источник
import java.io.BufferedReader; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws Exception { ProcessBuilder pb = new ProcessBuilder("script.bat"); pb.redirectErrorStream(true); Process p = pb.start(); BufferedReader logReader = new BufferedReader(new InputStreamReader(p.getInputStream())); String logLine = null; while ( (logLine = logReader.readLine()) != null) { System.out.println("Script output: " + logLine); } } }
Используя эту строку:
pb.redirectErrorStream(true);
мы можем объединить InputStream и ErrorStreamисточник
По умолчанию созданный подпроцесс не имеет собственного терминала или консоли. Все его стандартные операции ввода-вывода (т.е. 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
источник