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
| Método | Bloqueia? | Retorno | Uso |
|---|
output() | Sim | io::Result<Output> | Captura toda saída (stdout + stderr) |
status() | Sim | io::Result<ExitStatus> | Apenas código de saída |
spawn() | Não | io::Result<Child> | Processo filho assíncrono |
Tipos auxiliares
Output — contém stdout: Vec<u8>, stderr: Vec<u8> e status: ExitStatusExitStatus — 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())
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(())
}
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étodo | Descriçã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étodo | Descriçã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 |
stdin | Option<ChildStdin> — pipe de entrada |
stdout | Option<ChildStdout> — pipe de saída |
stderr | Option<ChildStderr> — pipe de erro |
Stdio
| Construtor | Descriçã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ção | Descriçã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(())
}
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