CodeGym /Cursos /JAVA 25 SELF /Lendo a saída do processo, trabalhando com stdin/stdout

Lendo a saída do processo, trabalhando com stdin/stdout

JAVA 25 SELF
Nível 61 , Lição 1
Disponível

1. Fluxos de entrada/saída do processo

Quando você inicia um processo externo a partir do Java, abre-se um verdadeiro mini-diálogo entre ele e o seu programa — três canais de comunicação.

O primeiro canal é a saída padrão. Tudo o que o processo externo quer mostrar “na tela” você pode capturar por meio de Process.getInputStream(). O segundo canal é o fluxo de erros. Se o processo decidir reclamar ou emitir uma mensagem de erro, você saberá disso através de Process.getErrorStream(). E, por fim, o terceiro é a entrada padrão. Por meio dele, você pode enviar dados ao processo, caso ele esteja aguardando algo de você. Isso é feito com Process.getOutputStream().

É um pouco confuso que os nomes dos fluxos pareçam invertidos. Para o processo, InputStream é aquilo que você lê (a saída dele), e OutputStream é para onde você escreve (a entrada dele). Mas se olharmos pelos olhos do seu programa Java, tudo fica lógico: você lê o que o processo produz e escreve o que quer enviar para ele.

Esquema dos fluxos do processo

+-------------------+           +---------------------+
|   Seu programa    | <-------> |   Processo externo  |
+-------------------+           +---------------------+
        |                                |
        |  getOutputStream()  -------->  stdin
        |  getInputStream()   <--------  stdout
        |  getErrorStream()   <--------  stderr

2. Leitura da saída do processo (stdout)

Vamos aprender a ler o que o processo externo imprime na tela. Pode ser o resultado de um comando, a versão de um programa, uma mensagem de sucesso — qualquer coisa.

Exemplo 1: Lendo a saída do comando java -version

Primeiro, vamos iniciar o processo e depois ler a sua saída.

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

public class ProcessOutputExample {
    public static void main(String[] args) throws IOException {
        // Criamos o processo: o comando depende do seu SO!
        ProcessBuilder pb = new ProcessBuilder("java", "-version");
        Process process = pb.start();

        // Lemos o fluxo de erros (java -version geralmente escreve lá!)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getErrorStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Process output: " + line);
            }
        }
    }
}

Atenção! O comando java -version escreve seu resultado não no stdout normal, mas no fluxo de erros stderr. Isso não é bug, é uma particularidade do Java. Por isso lemos process.getErrorStream(). Para a maioria dos outros comandos (por exemplo, echo, ls, dir) — use getInputStream().

Exemplo 2: Lendo a saída do 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 que precisamos de BufferedReader?

O fluxo de entrada padrão é um fluxo de bytes (InputStream). Para lê-lo como linhas, usamos a combinação InputStreamReader (converte bytes em caracteres) + BufferedReader (facilita a leitura linha a linha).

3. Escrita no fluxo de entrada do processo (stdin)

Às vezes o processo externo espera dados nossos. Por exemplo, se você executa um script que pede o nome do usuário ou uma calculadora que aguarda números.

Para isso, use process.getOutputStream() — sim, exatamente: você “escreve” no fluxo do processo.

Exemplo 3: Enviando dados para o processo

Suponha que você tenha um script Python simples que lê uma linha e a imprime de volta:

echo_input.py:

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

Vamos executá-lo a partir do Java e enviar uma linha:

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();

        // Escrevemos no stdin do processo
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(process.getOutputStream()))) {
            writer.write("Olá do Java!\n");
            writer.flush();
        }

        // Lemos o stdout do processo
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Resposta do processo: " + line);
            }
        }
    }
}

Não se esqueça de flush() — caso contrário, os dados podem “ficar presos” no buffer e não chegar ao processo.

4. Tratamento de erros: lendo o stderr

Um processo externo pode não apenas nos trazer resultados, mas também “reclamar da vida” — escrever erros. Se você não lê o fluxo de erros, pode perder informações importantes ou até sofrer um bloqueio (deadlock) se o buffer de erros encher.

Exemplo 4: Lendo o stderr do processo

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

// Lemos os erros
try (BufferedReader errorReader = new BufferedReader(
        new InputStreamReader(process.getErrorStream()))) {
    String line;
    while ((line = errorReader.readLine()) != null) {
        System.out.println("Erro do processo: " + line);
    }
}

Por que é importante ler ambos os fluxos?

Se o processo escreve muito em stdout ou stderr e você não lê esses fluxos, o buffer do SO pode encher e o processo “congelar”, aguardando alguém liberar espaço. Portanto, uma boa prática é ler ambos os fluxos em paralelo (ou pelo menos alternadamente).

5. Prática: executar comandos, ler a saída, tratar erros

Vamos juntar tudo o que vimos em uma pequena utilidade que executa um comando externo, lê sua saída padrão e erros e imprime tudo na tela.

Exemplo 5: Execução de comando universal

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("Informe um comando para executar, por exemplo: java RunCommand ls -l");
            return;
        }

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

        // Iniciamos threads separadas para ler stdout e stderr
        var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
        var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));

        int exitCode = process.waitFor(); // aguardamos o término do processo
        t1.join();
        t2.join();

        System.out.println("O processo terminou com o 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("Erro de leitura " + prefix + ": " + e.getMessage());
        }
    }
}

Essa abordagem evita deadlock e garante que nenhum erro seja perdido!

6. Um breve resumo sobre codificações

Por padrão, InputStreamReader usa a codificação do sistema. Se o processo externo escreve em outra codificação (por exemplo, UTF-8, e você está em Windows-1251), a saída pode aparecer com caracteres ilegíveis. Informe a codificação explicitamente:

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

7. Exemplo: executar um script Python e enviar dados para ele

Suponha que você tenha um script Python que espera várias linhas na entrada e depois imprime o resultado.

adder.py:

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

Do Java, você pode enviar os números e ler a resposta:

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 dois números para o stdin do script Python
        try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
            writer.println("10");
            writer.println("25");
        }

        // Lemos o resultado do stdout usando inputReader
        try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
            reader.lines().forEach(line -> System.out.println("Resposta do Python: " + line));
        }

        int exitCode = process.waitFor();
        System.out.println("O Python terminou com o código: " + exitCode);
    }
}

8. Por que ocorre deadlock? Como evitá-lo

Deadlock é a situação em que o processo e o seu programa ficam esperando um ao outro indefinidamente. Por exemplo, se o buffer de saída do processo encher e você não estiver lendo esse fluxo, o processo vai parar. Se você estiver esperando o término do processo e ele estiver esperando que você escreva algo para ele — também é um deadlock.

Como evitar:

  • Sempre leia ambos os fluxos (stdout e stderr).
  • Para processos longos/verbo­sos, use threads separadas para leitura.
  • Não bloqueie escrita e leitura simultaneamente na mesma thread.

9. Erros comuns ao trabalhar com os fluxos de E/S do processo

Erro nº 1: Ler apenas um dos fluxos (stdout ou stderr).
Se você ler apenas o stdout, e o processo escrever o erro em stderr, o buffer de erros pode encher — o processo vai travar. Solução: leia ambos os fluxos (de preferência em threads separadas).

Erro nº 2: Codificação incorreta.
Se você não especificar a codificação correta, a saída pode ficar ilegível. Sempre verifique em qual codificação o processo externo escreve (por exemplo, defina explicitamente "UTF-8" ou use StandardCharsets.UTF_8).

Erro nº 3: Não fechar os fluxos.
Fluxos abertos podem causar vazamento de recursos. Use try-with-resources ou feche todos os fluxos explicitamente.

Erro nº 4: Não chamar flush() ao escrever no stdin do processo.
Se você esquecer de chamar flush(), os dados podem não chegar ao processo — ele ficará esperando entrada para sempre.

Erro nº 5: Diferenças entre plataformas.
Comandos e sintaxe podem variar entre Windows e Linux/Mac. Verifique o SO via System.getProperty("os.name") e ajuste os comandos conforme necessário.

Erro nº 6: Não tratar exceções.
O trabalho com processos frequentemente envolve IOException. Sempre trate as exceções para que o programa não finalize de forma abrupta.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION