Módulo std::process em Rust

Referência completa do módulo std::process em Rust: Command, arg, output, spawn, status, piping stdin/stdout/stderr, exit e abort.

Módulo std::process em Rust

O módulo std::process permite executar programas externos, capturar sua saída, enviar dados via stdin, verificar o código de saída e controlar o ciclo de vida de processos filhos. O tipo central é Command, que fornece um builder para construir e executar comandos do sistema operacional.

Visão geral e tipos-chave

Command

Command é o builder principal para executar processos. Permite configurar:

  • O programa a executar e seus argumentos
  • Variáveis de ambiente
  • Diretório de trabalho
  • Redirecionamento de stdin, stdout e stderr

Formas de execução

MétodoBloqueia?RetornoUso
output()Simio::Result<Output>Captura toda saída (stdout + stderr)
status()Simio::Result<ExitStatus>Apenas código de saída
spawn()Nãoio::Result<Child>Processo filho assíncrono

Tipos auxiliares

  • Output — contém stdout: Vec<u8>, stderr: Vec<u8> e status: ExitStatus
  • ExitStatus — código de saída (.success(), .code())
  • Child — processo filho em execução (.wait(), .kill(), handles de I/O)
  • Stdio — configuração de stdin/stdout/stderr (piped(), null(), inherit())

Padrões comuns com código

Executar comando e capturar saída

use std::process::Command;
use std::io;

fn main() -> io::Result<()> {
    // output() executa e captura tudo
    let saida = Command::new("ls")
        .arg("-la")
        .arg("/tmp")
        .output()?;

    if saida.status.success() {
        let stdout = String::from_utf8_lossy(&saida.stdout);
        println!("Saída:\n{}", stdout);
    } else {
        let stderr = String::from_utf8_lossy(&saida.stderr);
        eprintln!("Erro (código {:?}):\n{}", saida.status.code(), stderr);
    }

    Ok(())
}

Verificar apenas o status

use std::process::Command;
use std::io;

fn main() -> io::Result<()> {
    // status() descarta a saída, retorna apenas o código
    let status = Command::new("cargo")
        .args(["build", "--release"])
        .status()?;

    if status.success() {
        println!("Build concluído com sucesso");
    } else {
        eprintln!("Build falhou com código: {:?}", status.code());
        std::process::exit(1);
    }

    Ok(())
}

Processo em background com spawn

use std::process::Command;
use std::io;

fn main() -> io::Result<()> {
    // spawn() retorna imediatamente com handle do processo filho
    let mut filho = Command::new("sleep")
        .arg("5")
        .spawn()?;

    println!("Processo filho PID: {}", filho.id());
    println!("Fazendo outras coisas enquanto espera...");

    // Esperar o processo terminar
    let status = filho.wait()?;
    println!("Processo terminou com: {:?}", status.code());

    Ok(())
}

Tabela de métodos e funções

Command (builder)

MétodoDescrição
Command::new(programa)Cria comando para o programa
arg(valor)Adiciona um argumento
args(valores)Adiciona múltiplos argumentos
env(chave, valor)Define variável de ambiente
envs(pares)Define múltiplas variáveis
env_remove(chave)Remove variável de ambiente
env_clear()Remove todas as variáveis herdadas
current_dir(path)Define diretório de trabalho
stdin(cfg)Configura stdin (Stdio)
stdout(cfg)Configura stdout (Stdio)
stderr(cfg)Configura stderr (Stdio)
output()Executa e captura saída
status()Executa e retorna status
spawn()Inicia processo e retorna Child

Child (processo filho)

MétodoDescrição
id()PID do processo
wait()Espera terminar, retorna ExitStatus
wait_with_output()Espera e captura saída
kill()Envia SIGKILL (encerra forçado)
try_wait()Verifica sem bloquear
stdinOption<ChildStdin> — pipe de entrada
stdoutOption<ChildStdout> — pipe de saída
stderrOption<ChildStderr> — pipe de erro

Stdio

ConstrutorDescrição
Stdio::piped()Cria pipe entre pai e filho
Stdio::null()Descarta (equivale a /dev/null)
Stdio::inherit()Herda do processo pai (padrão)

Funções de controle do processo

FunçãoDescrição
process::exit(code)Encerra com código (roda destrutores)
process::abort()Aborta imediatamente (sem destrutores)
process::id()PID do processo atual

Exemplos práticos

Exemplo 1: Piping entre processos (equivale a cmd1 | cmd2)

use std::io::{self, Write};
use std::process::{Command, Stdio};

fn main() -> io::Result<()> {
    // Equivalente a: echo "olá mundo\nrust é legal\nadeus" | grep "rust"

    let mut echo = Command::new("echo")
        .arg("olá mundo\nrust é legal\nadeus")
        .stdout(Stdio::piped())
        .spawn()?;

    let echo_stdout = echo.stdout.take().expect("stdout não capturado");

    let grep = Command::new("grep")
        .arg("rust")
        .stdin(echo_stdout) // stdin do grep = stdout do echo
        .output()?;

    echo.wait()?;

    let resultado = String::from_utf8_lossy(&grep.stdout);
    println!("Resultado do pipe: {}", resultado.trim());

    Ok(())
}

Exemplo 2: Enviar dados para stdin do processo filho

use std::io::{self, Write};
use std::process::{Command, Stdio};

fn executar_python(codigo: &str) -> io::Result<String> {
    let mut processo = Command::new("python3")
        .arg("-c")
        .arg(codigo)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let saida = processo.wait_with_output()?;

    if saida.status.success() {
        Ok(String::from_utf8_lossy(&saida.stdout).to_string())
    } else {
        let erro = String::from_utf8_lossy(&saida.stderr);
        Err(io::Error::new(
            io::ErrorKind::Other,
            format!("Python falhou: {}", erro),
        ))
    }
}

fn alimentar_stdin(programa: &str, dados: &str) -> io::Result<String> {
    let mut filho = Command::new(programa)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    // Escrever dados no stdin do processo filho
    if let Some(mut stdin) = filho.stdin.take() {
        stdin.write_all(dados.as_bytes())?;
        // stdin é fechado automaticamente quando sai de escopo (drop)
    }

    let saida = filho.wait_with_output()?;
    Ok(String::from_utf8_lossy(&saida.stdout).to_string())
}

fn main() -> io::Result<()> {
    // Executar código Python
    match executar_python("print(sum(range(100)))") {
        Ok(resultado) => println!("Python diz: {}", resultado.trim()),
        Err(e) => eprintln!("Erro: {}", e),
    }

    // Enviar dados para sort via stdin
    let dados = "banana\nmaça\nuva\nabacaxi\nlaranja\n";
    let ordenado = alimentar_stdin("sort", dados)?;
    println!("Ordenado:\n{}", ordenado);

    Ok(())
}

Exemplo 3: Executor de comandos com timeout

use std::io;
use std::process::Command;
use std::time::{Duration, Instant};
use std::thread;

struct ResultadoComando {
    stdout: String,
    stderr: String,
    codigo: Option<i32>,
    duracao: Duration,
    timeout: bool,
}

fn executar_com_timeout(
    programa: &str,
    args: &[&str],
    timeout: Duration,
) -> io::Result<ResultadoComando> {
    let inicio = Instant::now();

    let mut filho = Command::new(programa)
        .args(args)
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()?;

    // Verificar periodicamente se terminou
    loop {
        match filho.try_wait()? {
            Some(status) => {
                let saida = filho.wait_with_output()?;
                return Ok(ResultadoComando {
                    stdout: String::from_utf8_lossy(&saida.stdout).to_string(),
                    stderr: String::from_utf8_lossy(&saida.stderr).to_string(),
                    codigo: status.code(),
                    duracao: inicio.elapsed(),
                    timeout: false,
                });
            }
            None => {
                if inicio.elapsed() > timeout {
                    filho.kill()?;
                    filho.wait()?;
                    return Ok(ResultadoComando {
                        stdout: String::new(),
                        stderr: format!("Timeout após {:?}", timeout),
                        codigo: None,
                        duracao: inicio.elapsed(),
                        timeout: true,
                    });
                }
                thread::sleep(Duration::from_millis(100));
            }
        }
    }
}

fn main() -> io::Result<()> {
    let resultado = executar_com_timeout("ls", &["-la", "/"], Duration::from_secs(5))?;

    if resultado.timeout {
        eprintln!("Comando excedeu o timeout!");
    } else {
        println!("Saída ({:?}, código {:?}):", resultado.duracao, resultado.codigo);
        println!("{}", resultado.stdout);
    }

    Ok(())
}

Exemplo 4: Build script que executa múltiplos comandos

use std::io;
use std::process::{Command, ExitStatus};

fn executar(descricao: &str, programa: &str, args: &[&str]) -> io::Result<ExitStatus> {
    println!("\n--- {} ---", descricao);
    println!("$ {} {}", programa, args.join(" "));

    let status = Command::new(programa)
        .args(args)
        .status()?;

    if !status.success() {
        eprintln!("FALHA: {} retornou {:?}", programa, status.code());
    }

    Ok(status)
}

fn pipeline_build(projeto: &str) -> io::Result<bool> {
    let passos: Vec<(&str, &str, Vec<&str>)> = vec![
        ("Verificar formatação", "cargo", vec!["fmt", "--check"]),
        ("Analisar código", "cargo", vec!["clippy", "--", "-D", "warnings"]),
        ("Executar testes", "cargo", vec!["test"]),
        ("Build de release", "cargo", vec!["build", "--release"]),
    ];

    println!("=== Pipeline de build para '{}' ===", projeto);

    for (descricao, programa, args) in &passos {
        let status = executar(descricao, programa, args)?;
        if !status.success() {
            eprintln!("\nPipeline falhou em: {}", descricao);
            return Ok(false);
        }
    }

    println!("\n=== Pipeline concluído com sucesso ===");
    Ok(true)
}

fn main() {
    match pipeline_build("meu_projeto") {
        Ok(true) => std::process::exit(0),
        Ok(false) => std::process::exit(1),
        Err(e) => {
            eprintln!("Erro no pipeline: {}", e);
            std::process::exit(2);
        }
    }
}

Exemplo 5: Capturar saída em tempo real (streaming)

use std::io::{self, BufRead, BufReader};
use std::process::{Command, Stdio};

fn executar_streaming(programa: &str, args: &[&str]) -> io::Result<i32> {
    let mut filho = Command::new(programa)
        .args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    // Ler stdout em tempo real
    let stdout = filho.stdout.take().expect("stdout não capturado");
    let stderr = filho.stderr.take().expect("stderr não capturado");

    let stdout_thread = std::thread::spawn(move || {
        let leitor = BufReader::new(stdout);
        for linha in leitor.lines() {
            if let Ok(l) = linha {
                println!("[STDOUT] {}", l);
            }
        }
    });

    let stderr_thread = std::thread::spawn(move || {
        let leitor = BufReader::new(stderr);
        for linha in leitor.lines() {
            if let Ok(l) = linha {
                eprintln!("[STDERR] {}", l);
            }
        }
    });

    let status = filho.wait()?;
    stdout_thread.join().unwrap();
    stderr_thread.join().unwrap();

    Ok(status.code().unwrap_or(-1))
}

fn main() -> io::Result<()> {
    println!("Executando 'cargo test' com saída em tempo real:");
    let codigo = executar_streaming("cargo", &["test"])?;
    println!("\nProcesso terminou com código: {}", codigo);
    Ok(())
}

Padrões de tratamento de erro para I/O

Programa não encontrado

use std::io;
use std::process::Command;

fn executar_seguro(programa: &str, args: &[&str]) -> io::Result<String> {
    match Command::new(programa).args(args).output() {
        Ok(saida) => {
            if saida.status.success() {
                Ok(String::from_utf8_lossy(&saida.stdout).to_string())
            } else {
                let stderr = String::from_utf8_lossy(&saida.stderr);
                Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("{} falhou ({}): {}", programa, saida.status, stderr.trim()),
                ))
            }
        }
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            Err(io::Error::new(
                io::ErrorKind::NotFound,
                format!(
                    "Programa '{}' não encontrado. Verifique se está instalado e no PATH.",
                    programa
                ),
            ))
        }
        Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
            Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                format!("Sem permissão para executar '{}'", programa),
            ))
        }
        Err(e) => Err(e),
    }
}

exit() vs abort()

use std::process;

fn encerrar_limpo() {
    // exit() roda destrutores e handlers de saída
    // Código 0 = sucesso, != 0 = erro
    process::exit(0);
}

fn encerrar_emergencia() {
    // abort() termina imediatamente — sem destrutores, sem cleanup
    // Use apenas em situações críticas (corrupção de memória, etc.)
    process::abort();
}

Dicas de desempenho

  • Use status() se não precisar da saída — evita alocações de Vec<u8> para stdout/stderr.
  • Feche o stdin do processo filho o mais cedo possível (via drop()) — alguns programas esperam EOF no stdin para começar a processar.
  • Para múltiplos comandos encadeados, considere executar via sh -c "cmd1 | cmd2" em vez de construir pipes manualmente.
  • Prefira spawn() + wait_with_output() a output() quando precisar enviar dados para stdin — output() não permite acesso ao stdin.

Veja também