CodeGym /Cursos /JAVA 25 SELF /Lectura de la salida de un proceso, trabajo con stdin/std...

Lectura de la salida de un proceso, trabajo con stdin/stdout

JAVA 25 SELF
Nivel 61, Lección 1
Disponible

1. Flujos de entrada/salida del proceso

Cuando lanzas un proceso externo desde Java, entre él y tu programa se abre un auténtico minidiálogo — tres canales de comunicación.

El primer canal — es la salida estándar. Todo lo que el proceso externo quiere mostrar «en la pantalla», puedes capturarlo a través de Process.getInputStream(). El segundo canal — el flujo de errores. Si el proceso decide quejarse o emitir un mensaje de error, te enterarás de ello mediante Process.getErrorStream(). Y por último, el tercero — la entrada estándar. Por ella puedes enviarle datos al proceso, si está esperando algo de ti. Se hace con Process.getOutputStream().

Resulta un poco confuso que los nombres de los flujos parezcan intercambiados. Para el proceso, InputStream es lo que tú lees (su salida), y OutputStream es donde tú escribes (su entrada). Pero si lo miras desde tu programa Java, todo resulta lógico: lees lo que el proceso emite y escribes lo que quieres pasarle.

Esquema de los flujos del proceso

+-------------------+           +---------------------+
|      Tu programa  | <-------> |    Proceso externo  |
+-------------------+           +---------------------+
        |                                |
        |  getOutputStream()  -------->  stdin
        |  getInputStream()   <--------  stdout
        |  getErrorStream()   <--------  stderr

2. Lectura de la salida del proceso (stdout)

Aprendamos a leer lo que el proceso externo imprime en pantalla. Puede ser el resultado de un comando, la versión del programa, un mensaje de éxito — lo que sea.

Ejemplo 1: Lectura de la salida del comando java -version

Primero lanzaremos el proceso y luego leeremos su salida.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class ProcessOutputExample {
    public static void main(String[] args) throws IOException {
        // Creamos el proceso: ¡el comando depende de tu SO!
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Process process = pb.start();

        // Leemos el flujo de errores (¡java -version suele escribir ahí!)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Process output: " + line);
            }
        }
    }
}

¡Atención! El comando java -version escribe su resultado no en el stdout habitual, sino en el flujo de errores stderr. No es un bug, sino una característica de Java. Por eso leemos process.getErrorStream(). Para la mayoría de los demás comandos (por ejemplo, echo, ls, dir) usa getInputStream().

Ejemplo 2: Lectura de la salida del comando echo

ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
Process process = pb.start();

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getInputStream()))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println("Process output: " + line);
    }
}

¿Por qué hace falta BufferedReader?

El flujo de entrada estándar es un flujo de bytes (InputStream). Para leerlo como cadenas, usamos la combinación de InputStreamReader (convierte bytes en caracteres) + BufferedReader (permite leer cómodamente línea a línea).

3. Escritura en la entrada del proceso (stdin)

A veces el proceso externo espera datos de nosotros. Por ejemplo, si ejecutas un script que pide introducir un nombre de usuario o una calculadora que espera números.

Para ello usa process.getOutputStream() — sí: tú «escribes» en el flujo del proceso.

Ejemplo 3: Enviar datos a un proceso

Supón que tienes un script sencillo de Python que lee una línea y la imprime de vuelta:

echo_input.py:

# echo_input.py
line = input()
print("You said:", line)

Lo lanzamos desde Java y le enviamos una línea:

import java.io.*;

public class ProcessStdinExample {
    public static void main(String[] args) throws IOException {
        ProcessBuilder pb = new ProcessBuilder("python", "echo_input.py");
        Process process = pb.start();

        // Escribimos en el stdin del proceso
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(process.getOutputStream()))) {
            writer.write("¡Hola desde Java!\n");
            writer.flush();
        }

        // Leemos el stdout del proceso
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Respuesta del proceso: " + line);
            }
        }
    }
}

No olvides flush() — de lo contrario, los datos pueden «quedarse» en el búfer y no llegar al proceso.

4. Manejo de errores: lectura de stderr

Un proceso externo no solo puede darnos resultados, también puede quejarse — escribir errores. Si no lees el flujo de errores, puedes perder información importante o incluso provocar un bloqueo (deadlock) si el búfer de errores se llena.

Ejemplo 4: Leer stderr del proceso

ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();

// Leemos los errores
try (BufferedReader errorReader = new BufferedReader(
        new InputStreamReader(process.getErrorStream()))) {
    String line;
    while ((line = errorReader.readLine()) != null) {
        System.out.println("Error del proceso: " + line);
    }
}

¿Por qué es importante leer ambos flujos?

Si el proceso escribe mucho en stdout o stderr y tú no lees esos flujos, el búfer del SO puede llenarse, y el proceso «se quedará colgado» esperando a que alguien libere espacio. Por eso, es buena práctica leer ambos flujos en paralelo (o al menos de forma alterna).

5. Práctica: lanzar un comando, leer la salida y manejar errores

Vamos a unir todo lo anterior en una pequeña utilidad que lanza un comando externo, lee su salida estándar y sus errores, y lo muestra todo por pantalla.

Ejemplo 5: Ejecutor universal de comandos

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class RunCommand {
    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            System.out.println("Especifique un comando para ejecutar, por ejemplo: java RunCommand ls -l");
            return;
        }

        var process = new ProcessBuilder(args).start();

        // Lanzamos hilos separados para leer stdout y stderr
        var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
        var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));

        int exitCode = process.waitFor(); // esperamos a que el proceso termine
        t1.join();
        t2.join();

        System.out.println("El proceso finalizó con el código: " + exitCode);
    }

    private static void readStream(BufferedReader reader, String prefix) {
        try (reader) {
            reader.lines().forEach(line -> System.out.println(prefix + " " + line));
        } catch (Exception e) {
            System.err.println("Error de lectura " + prefix + ": " + e.getMessage());
        }
    }
}

Este enfoque permite evitar el deadlock y no perder ningún error.

6. Breve sobre codificaciones

Por defecto, InputStreamReader usa la codificación del sistema. Si el proceso externo escribe con otra codificación (por ejemplo, UTF-8 y en tu sistema es Windows-1251), la salida puede mostrarse como «caracteres ilegibles». Especifica la codificación de forma explícita:

new InputStreamReader(process.getInputStream(), "UTF-8")

7. Ejemplo: ejecutar un script de Python y pasarle datos

Supón que tienes un script de Python que espera varias líneas por la entrada y luego imprime el resultado.

adder.py:

# adder.py
a = int(input())
b = int(input())
print(a + b)

Desde Java puedes enviarle números y leer la respuesta:

import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;

public class PythonAdder {
    public static void main(String[] args) throws Exception {
        var process = new ProcessBuilder("python", "adder.py").start();

        // Enviamos dos números al stdin del script de Python
        try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
            writer.println("10");
            writer.println("25");
        }

        // Leemos el resultado desde stdout con inputReader
        try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
            reader.lines().forEach(line -> System.out.println("Respuesta de Python: " + line));
        }

        int exitCode = process.waitFor();
        System.out.println("Python terminó con el código: " + exitCode);
    }
}

8. ¿Por qué se produce un interbloqueo (deadlock)? Cómo evitarlo

Un deadlock es una situación en la que el proceso y tu programa se esperan mutuamente indefinidamente. Por ejemplo, si el búfer de salida del proceso se llena y tú no lees ese flujo, el proceso se detendrá. Si esperas a que el proceso termine y él espera a que tú le escribas algo, también hay deadlock.

Cómo evitarlo:

  • Lee siempre ambos flujos (stdout y stderr).
  • Para procesos largos o muy verbosos, usa hilos separados para la lectura.
  • No bloquees escritura y lectura al mismo tiempo en un mismo hilo.

9. Errores típicos al trabajar con los flujos de entrada/salida del proceso

Error n.º 1: leer solo uno de los flujos (stdout o stderr).
Si solo lees stdout y el proceso escribe el error en stderr, el búfer de errores puede llenarse — el proceso se colgará. Solución: lee ambos flujos (mejor en hilos separados).

Error n.º 2: codificación incorrecta.
Si no especificas la codificación adecuada, la salida puede ser ilegible. Comprueba siempre en qué codificación escribe el proceso externo (por ejemplo, especifica explícitamente "UTF-8" o usa StandardCharsets.UTF_8).

Error n.º 3: no cerrar los flujos.
Los flujos abiertos pueden provocar fugas de recursos. Usa try-with-resources o cierra todos los flujos explícitamente.

Error n.º 4: no llamar a flush() al escribir en el stdin del proceso.
Si olvidas llamar a flush(), los datos pueden no llegar al proceso — este esperará la entrada para siempre.

Error n.º 5: diferencias entre plataformas.
Los comandos y la sintaxis pueden variar entre Windows y Linux/Mac. Comprueba el SO con System.getProperty("os.name") y elige los comandos en consecuencia.

Error n.º 6: no manejar las excepciones.
Trabajar con procesos suele ir acompañado de IOException. Maneja siempre las excepciones para que el programa no termine de forma abrupta.

1
Tarea
JAVA 25 SELF, nivel 61, lección 1
Bloqueada
Mensaje secreto 📩
Mensaje secreto 📩
1
Tarea
JAVA 25 SELF, nivel 61, lección 1
Bloqueada
Controlador de la Estación Espacial 🚀
Controlador de la Estación Espacial 🚀
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION