java - para - ProcessBuilder: Reenvío de stdout y stderr de procesos iniciados sin bloquear el hilo principal
process java example (7)
Estoy construyendo un proceso en Java usando ProcessBuilder de la siguiente manera:
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
.redirectErrorStream(true);
Process p = pb.start();
InputStream stdOut = p.getInputStream();
Ahora mi problema es el siguiente: me gustaría capturar lo que esté pasando por stdout y / o stderr de ese proceso y redirigirlo a System.out
asincrónicamente. Quiero que el proceso y su redirección de salida se ejecuten en segundo plano. Hasta ahora, la única forma que he encontrado para hacer esto es generar manualmente un nuevo hilo que leerá continuamente de stdOut
y luego llamar al método write()
apropiado de 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();
Si bien ese enfoque funciona, se siente un poco sucio. Y además de eso, me da un hilo más para administrar y terminar correctamente. ¿Hay alguna forma mejor de hacer esto?
Es tan simple como seguir:
File logFile = new File(...);
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(logFile);
por .redirectErrorStream (verdadero) le dices al proceso fusionar error y flujo de salida y luego por .redirectOutput (archivo) redirige salida fusionada a un archivo.
Actualizar:
Logré hacer esto de la siguiente manera:
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
}
Ahora puede ver las dos salidas de los hilos principal y asyncOut en System.out
Por defecto, el subproceso creado no tiene su propio terminal o consola. Todas sus operaciones de E / S estándar (es decir, stdin, stdout, stderr) se redirigirán al proceso principal, donde se puede acceder a ellas a través de las secuencias obtenidas utilizando los métodos getOutputStream (), getInputStream () y getErrorStream (). El proceso principal utiliza estas secuencias para alimentar la entrada y obtener resultados del subproceso. Debido a que algunas plataformas nativas solo proporcionan un tamaño de búfer limitado para flujos de entrada y salida estándar, si no se escribe rápidamente el flujo de entrada o se lee el flujo de salida del subproceso, el subproceso puede bloquearse o incluso bloquearse.
Solo en Java 6 o anterior es con un llamado StreamGobbler
(que está comenzando a crear):
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();
}
}
}
Para Java 7, vea la respuesta de Evgeniy Dorofeev.
Una solución flexible con Java 8 lambda que le permite proporcionar un Consumer
que procesará la salida (por ejemplo, registrarla) línea por línea. run()
es un trazador de líneas sin excepciones marcadas. De forma alternativa a la implementación de Runnable
, puede extender Thread
lugar de otras respuestas.
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);
}
}
Puede usarlo, por ejemplo, de esta manera:
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();
}
Aquí la secuencia de salida se redirige a System.out
y la secuencia de errores se registra en el nivel de error por el logger
.
Use ProcessBuilder.inheritIO
, establece que el origen y el destino para la E / S estándar del subproceso sean los mismos que los del proceso Java actual.
Process p = new ProcessBuilder().inheritIO().command("command1").start();
Si Java 7 no es una opción
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();
}
Las hebras morirán automáticamente cuando termine el subproceso, porque src
será EOF.
Yo también puedo usar solo Java 6. Utilicé la implementación del escaner de hilos de @ EvgeniyDorofeev. En mi código, después de que un proceso finaliza, tengo que ejecutar inmediatamente otros dos procesos que comparan cada uno de los resultados redirigidos (una prueba unitaria basada en diferencias para garantizar que stdout y stderr sean los mismos que los bendecidos).
Los subprocesos del escáner no terminan lo suficientemente rápido, incluso si espero (>) el proceso para completar. Para que el código funcione correctamente, debo asegurarme de que los hilos se unan después de que el proceso finalice.
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();
Su código personalizado va en lugar de ...