ProcessBuilder: reenvío stdout y stderr de procesos iniciados sin bloquear el hilo principal

Resuelto LordOfThePigs asked hace 11 años • 12 respuestas

Estoy creando 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 todo lo que esté pasando por stdout y/o stderr de ese proceso y redirigirlo de forma System.outasincrónica. 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 stdOuty luego llamará al write()método 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 encima me da un hilo más para gestionar y terminar correctamente. ¿Hay alguna mejor manera de hacer esto?

LordOfThePigs avatar Jan 05 '13 04:01 LordOfThePigs
Aceptado

Use ProcessBuilder.inheritIO, establece el origen y el destino para la E/S estándar del subproceso para que 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();
}

Los subprocesos morirán automáticamente cuando finalice el subproceso, porque srcEOF.

Evgeniy Dorofeev avatar Jan 05 '2013 02:01 Evgeniy Dorofeev

Para Java 7 y posteriores , consulte la respuesta de Evgeniy Dorofeev .

Para Java 6 y versiones anteriores , cree y utilice un 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();
        }
    }
}
asgoth avatar Jan 04 '2013 21:01 asgoth