CodeGym /Cursos /JAVA 25 SELF /ProcessBuilder — execução de processos externos

ProcessBuilder — execução de processos externos

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

1. O que são processos no SO e por que executá-los a partir do Java

Processos: da JVM ao bash e vice-versa

Quando você liga o computador e abre um navegador, um aplicativo de mensagens ou um jogo — tudo isso são processos separados. O sistema operacional inicia para cada programa seu próprio “mini-mundo”: aloca memória, tempo de CPU, permite trabalhar com arquivos e rede. Assim, os programas coexistem sem atrapalhar uns aos outros.

Um programa Java não é exceção. Quando você executa java MyApp, o sistema cria para ele um processo separado com todos os recursos necessários. Dentro dele, seu programa já está em execução: calcula, desenha, lê arquivos — tudo o que for necessário.

Mas às vezes só Java não basta. Às vezes é preciso pedir ajuda a outro programa — iniciar um compactador para empacotar arquivos, chamar o ffmpeg para processar um vídeo, ou simplesmente descobrir qual versão do Java está instalada no computador. Isso é a execução de um processo externo: o Java diz ao sistema “execute esta utilidade para mim” e depois obtém o resultado do seu trabalho.

Essencialmente, é uma forma de tornar o programa mais flexível: combinar ferramentas diferentes, automatizar tarefas repetitivas ou incorporar sua lógica a processos do sistema já existentes. Às vezes é mais simples pedir que um comando externo faça parte do trabalho do que reinventar a roda no código.

JVM vs. processo externo

Processo da JVM — seu programa em execução na Máquina Virtual Java.

Processo externo — qualquer outro programa: calculadora, script Python, linha de comando, até outro processo Java.

2. A classe ProcessBuilder

Nos “tempos antigos” o Java executava processos via o método Runtime.getRuntime().exec(). Não era a maneira mais conveniente nem segura — como tentar pregar um prego com um microscópio. Por isso, a partir do Java 5 surgiu a classe ProcessBuilder, que permite criar, configurar e iniciar processos externos de forma mais flexível e clara.

ProcessBuilder — é como um “construtor” que permite definir antecipadamente todos os parâmetros do processo: comando, argumentos, diretório de trabalho, variáveis de ambiente etc.

Sintaxe: criação de processo

ProcessBuilder pb = new ProcessBuilder("comando", "argumento1", "argumento2", ...);
  • O primeiro argumento — o nome do comando (por exemplo, "ls", "dir", "ping", "java").
  • Os demais — os parâmetros do comando.

Exemplo: executar o comando ls (Linux/Mac) ou dir (Windows)

ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
    pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
    pb = new ProcessBuilder("ls", "-l");
}

Aliás, no Windows comandos como dir, copy etc. — não são executáveis separados, mas comandos “integrados” à linha de comando (cmd.exe). Por isso devem ser executados via cmd.exe /c ....

Exemplo: iniciar um processo simples

ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");

3. Configuração do ambiente do processo

Passagem de argumentos. Os argumentos do comando são passados como strings separadas:

ProcessBuilder pb = new ProcessBuilder("ping", "google.com");

Definição do diretório de trabalho. Por padrão, o processo é iniciado na mesma pasta que o seu programa. Mas é possível indicar explicitamente outro diretório:

pb.directory(new java.io.File("/tmp"));      // Para Linux/Mac
pb.directory(new java.io.File("C:\\Temp"));  // Para Windows

Alterando variáveis de ambiente. Cada processo tem seu próprio conjunto de variáveis de ambiente. É possível adicioná-las ou modificá-las:

pb.environment().put("MY_VAR", "HelloFromJava");

Isso pode ser útil se o processo externo espera variáveis específicas.

4. Iniciando o processo

Método start(). Quando tudo estiver configurado, é hora de iniciar o processo:

Process process = pb.start();

O método start() retorna um objeto Process, que permite gerenciar o programa iniciado: ler sua saída, escrever em sua entrada, encerrá-lo etc.

Tratamento de exceções. start() pode lançar IOException se o comando não for encontrado, se não houver permissões ou ocorrer outro erro de inicialização.

Exemplo:

try {
    Process process = pb.start();
    // Trabalhamos com o processo...
} catch (IOException e) {
    System.out.println("Erro ao iniciar o processo: " + e.getMessage());
}

5. Prática: iniciando comandos simples

Exemplo 1: listar os arquivos na pasta

import java.io.*;

public class ProcessDemo {
    public static void main(String[] args) {
        // Definimos o comando de acordo com o SO
        ProcessBuilder pb;
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            pb = new ProcessBuilder("cmd.exe", "/c", "dir");
        } else {
            pb = new ProcessBuilder("ls", "-l");
        }

        try {
            Process process = pb.start();

            // Lemos a saída do processo (stdout)
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream())
            );
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            // Aguardamos a finalização do processo
            int exitCode = process.waitFor();
            System.out.println("Processo finalizado com código: " + exitCode);

        } catch (IOException | InterruptedException e) {
            System.out.println("Erro: " + e.getMessage());
        }
    }
}

O que acontece aqui?

  • Detectamos qual é o SO — para escolher o comando correto.
  • Criamos o ProcessBuilder com o comando necessário.
  • Iniciamos o processo com start().
  • Lemos as linhas do stdout do processo e as exibimos na tela.
  • Aguardamos a finalização do processo (waitFor()).
  • Exibimos o código de saída (0 — sucesso; outro valor — erro).

Exemplo 2: executar java -version

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

    // java -version escreve em stderr, então lemos getErrorStream()
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getErrorStream())
    );
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
    process.waitFor();
} catch (IOException | InterruptedException e) {
    e.printStackTrace();
}

Detalhe importante: Alguns comandos (por exemplo, java -version) não escrevem na saída padrão (stdout), mas no fluxo de erros (stderr). Portanto, às vezes é preciso ler process.getErrorStream().

6. Multiplataforma: diferenças entre Windows e Linux/Mac

  • Os comandos e seus parâmetros podem ser diferentes.
  • Os caminhos de arquivos são escritos de forma diferente (C:\Temp vs /tmp).
  • Alguns comandos (por exemplo, ls, cat) existem apenas em sistemas tipo Unix, e no Windows há equivalentes (dir, type).
  • No Windows, comandos integrados são executados apenas via cmd.exe /c comando.

Exemplo de verificação do SO:

String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
    // Windows
} else if (os.contains("mac")) {
    // macOS
} else if (os.contains("nix") || os.contains("nux")) {
    // Linux
}

Dica: Sempre teste seus programas nos SOs-alvo se você precisa de compatibilidade multiplataforma.

7. Tabela: principais métodos e recursos do ProcessBuilder

Método/campo Finalidade Exemplo de uso
new ProcessBuilder(String...)
Criar um processo com comando e argumentos
new ProcessBuilder("ls", "-l")
.directory(File)
Definir o diretório de trabalho
.directory(new File("/tmp"))
.environment()
Obter/alterar variáveis de ambiente
.environment().put("VAR", "value")
.start()
Iniciar o processo
Process p = pb.start()
Process.getInputStream()
Obter o stdout do processo
InputStream
Process.getErrorStream()
Obter o stderr do processo
InputStream
Process.getOutputStream()
Obter o stdin do processo
OutputStream
Process.waitFor()
Aguardar a finalização do processo
int code = p.waitFor()
Process.exitValue()
Obter o código de saída do processo
int code = p.exitValue()

8. Erros comuns ao iniciar processos externos

Erro nº 1: comando não encontrado. Se você errou o nome do comando ou ele não existe no sistema, receberá IOException: Cannot run program .... Por exemplo, tentar executar ls no Windows.

Erro nº 2: passagem incorreta de argumentos. Não junte todo o comando em uma única string! Correto: new ProcessBuilder("ping", "google.com"). Incorreto: new ProcessBuilder("ping google.com").

Erro nº 3: não considerar as diferenças do SO. Um comando que funciona muito bem no Linux pode não existir no Windows e vice-versa. Sempre verifique o SO e adapte o comando.

Erro nº 4: não tratar a saída do processo. Se você não ler a saída do processo, ele pode “travar” por causa do estouro de buffer. Mesmo que você não vá usar a saída — leia-a e, por exemplo, apenas descarte.

Erro nº 5: não fechar os streams. Os streams do processo precisam ser fechados após o uso para evitar vazamentos de recursos.

Erro nº 6: não tratar as exceções. Iniciar um processo externo é arriscado. Sempre use try-catch e informe o usuário sobre os erros.

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