Skip to content

3 Flujos de comunicación

Flujos de comunicación entre proceso padre y subproceso

Por defecto, los subprocesos hijos creados por un proceso padre no tienen su propio terminal y console. Todas la operaciones con le entrada y salida estándar (stdin, stdout, stderr) realizadas por el subproceso hijo será redirigidas hacia el proceso padre, a través de una tubería (pipe) entre ambos.

Para acceder a la salida del subproceso hijo desde el proceso padre, debemos leer del flujo de entrada (ojo, no el de salida) que va del subproceso hijo al proceso padre, que puede obtener mediante el método getInputStream() del objeto Process correspondiente al subproceso hijo.

De igual forma, si queremos que el subproceso hijo reciba alguna información desde el proceso padre, éste deberá escribir en el flujo de salida (ojo, no el de entrada) que va del proceso padre al subproceso hijo, que puede obtener mediante el método getOutputStream() del objeto Process correspondiente al subproceso hijo.

Finalmente, podremos leer del flujo de error que va desde el subproceso hijo al proceso padre, que podemos obtener mediante el método getErrorStream() del objeto Process correspondiente al subproceso hijo.

En el siguiente ejemplo construimos un programa que al ejecutarse crea un subproceso que usa el intérprete de comandos de Windows para listar el directorio actual. El proceso padre leerá la salida del subproceso y la escribirá por pantalla.

void showLibrary() throws IOException {
    ArrayList<String> command = new ArrayList<>(Arrays.asList("ls", "-ltra", "/Library"));
    ProcessBuilder processBuilder = new ProcessBuilder(command);
    Process process = null;
    process = processBuilder.start();
    System.out.printf("Receiving from command %s ...\n\n", String.join(" ", command));
    try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
        process.getInputStream()))) {
        bufferedReader.lines().forEach(System.out::println);
    }
}

Un aspecto muy importante de los flujos de comunicación entre proceso padre y subproceso es que la escritura y lectura en dichos flujos son bloqueantes, es decir, que cuando el proceso padre realiza una operación de lectura sobre el flujo de salida del subproceso hijo a través su InputStream queda bloqueado hasta que el hijo le devuelve los datos requeridos, lo que permite que para la comunicación no sea necesario establecer mecanismos adicionales de sincronización (de hecho en el ejemplo anterior no los hemos establecido).

Redirección de los flujos del subproceso

Antes de comenzar la ejecución de un subproceso, podemos hacer que su entrada, salida o salida de error sea redirigida automáticamente a otro destino. Para ello, la clase ProcessBuilder proporciona los métodos redirectInput(redirect), redirectOutput(redirect) y redirectError(redirect), disponibles desde Java 7. Estos métodos reciben un objeto de la clase ProcessBuilder.Redirect, que representa un destino de la redirección.

La clase ProcessBuilder.Redirect define una serie de valor especiales:

  • Redirect.PIPE: El destinatario será el proceso padre. Es el valor por defecto.
  • Redirect.INHERIT: El destinatario será el mismo que el del proceso padre.
  • Redirect.DISCARD: No habrá destinatario. El flujo será descartado.

Y también proporciona una serie de métodos factoría para crear una redirección a partir de un archivo:

  • Redirect.from(file): El destinatario será un fichero del que se leerá.
  • Redirect.to(File): El destinatario será un fichero en el que se escribirá.
  • Redirect.appendTo(File): El destinatario será un fichero en el que se escribirá al final sin machacar el contenido original.

La clase ProcessBuilder tiene sobrecargados los métodos redirectInput(file), redirectOutput(file) y redirectError(file), de manera que puedan recibir directamente un fichero, siendo equivalentes, respectivamente, a redirectInput(Redirect.from(file)), redirectOutput(Redirect.to(file)) y redirectError(Redirect.to(file)).

Si queremos redireccionar tanto la entrada, com la salida y la salida de error a los mismos destinatarios que tenga el proceso padre, podemos usar el método inheritIO() de la clase ProcessBuilder, que es equivalente a llamar a redirectInput(Redirect.INHERIT), .redirectOutput(Redirect.INHERIT)y .redirectError(Redirect.INHERIT).

Como hemos visto, por defecto, la salida estándar y la salida de error del subprocesos son enviadas a dos flujos distintos. Sin embargo, podemos establecer que ambas salidas se envíen combinadas al mismo flujo, para lo que utilizaremos el método redirectErrorStream(true) del objeto ProcessBuilder. El flujo combinado será accesible a través del método getInputStream() del objeto Process, y el método getErrorStream() retornará un flujo nulo. Si hemos combinado salida estándar y salida de error, el flujo combinado puede ser redirigido mediante redirectOutput(redirect) o redirectOutput(file).

Veamos un ejemplo en el que se redirecciona el flujo de salida hacia un fichero y el flujo de error hacia el mismo destino que el del padre:

void redirectToFileTest() throws IOException {
    File outFile = new File("out.tmp");
    Process p = new ProcessBuilder("ls", "-la")
        .redirectOutput(outFile)
        .redirectError(ProcessBuilder.Redirect.INHERIT)
        .start();
}