Capítulo 20 16 min de leitura

Processo

stdlib::process é uma API no estilo builder para fazer spawn de comandos externos com controle total sobre argv: saída capturada, sobrescrita de variáveis de ambiente, diretório de trabalho, stdin via pipe, códigos de saída e pipes de leitura/escrita em tempo real. Internamente usa fork+execvp no POSIX e CreateProcess no Windows — não há shell intermediário, ou seja, não há risco de quoting incorreto nem superfície de ataque por injeção de shell.

Import

import stdlib::process::*;

Visão geral

O fluxo típico é: construir um Command, configurá-lo com métodos encadeados e depois chamar run() (síncrono, bloqueia até o processo terminar) ou spawn() (não bloqueante, retorna um Child).

let cmd: *Command = Command::new("git");
cmd.arg("rev-parse").arg("HEAD").capture_out();
let r: !ProcessResult = cmd.run();
if r.ok {
    println!("HEAD =", r.val.stdout.trim());
}
cmd.free();

Superfície pública em resumo

Item Tipo Resumo
ProcessResult struct Resultado de uma execução concluída (código de saída, saída capturada, informações de sinal).
Command struct + builder Configura e inicia um subprocesso.
Child struct Handle para um processo em execução após spawn.
ChildStdin / ChildStdout / ChildStderr struct Handles de pipe em tempo real expostos após spawn().
Command::new / arg / args / env / env_clear / cwd / stdin_str fn builder Configura programa, argumentos, variáveis de ambiente, diretório e stdin.
Command::capture_out / capture_err / capture fn builder Solicita captura de saída (consumida por run()).
Command::pipe_stdin / pipe_stdout / pipe_stderr fn builder Solicita pipes em tempo real (consumidos por spawn()).
Command::run fn Executa de forma síncrona; retorna !ProcessResult.
Command::spawn fn Inicia sem aguardar; retorna !Child.
Command::free fn Libera o builder.
Child::wait / kill fn Bloqueia aguardando saída / envia sinal ao filho.
ChildStdin::write / close fn Alimenta e fecha o stdin do processo filho.
ChildStdout::read / read_all / close fn Lê o stdout do processo filho.
ChildStderr::read / read_all / close fn Lê o stderr do processo filho.
process_kill fn Envia sinal a um PID arbitrário.
process_exists fn Verifica se um PID está vivo.

Tipos de resultado e configuração

ProcessResult

O resultado de uma execução concluída. Retornado por Command::run() e Child::wait().

pub struct ProcessResult {
    pub exit_code: i32,   // 0 = sucesso
    pub stdout: string,   // vazio se não capturado
    pub stderr: string,   // vazio se não capturado
    pub signaled: bool,   // true se o filho morreu por um sinal (POSIX)
    pub signal: i32,      // número do sinal; 0 se !signaled
}
Campo Tipo Significado
exit_code i32 Status de saída do processo; 0 indica sucesso. No POSIX, morte por sinal é reportada como 128 + sinal.
stdout string stdout capturado, ou "" se nenhum flag de captura foi definido.
stderr string stderr capturado, ou "" se nenhum flag de captura foi definido.
signaled bool true se o processo filho foi encerrado por um sinal (somente POSIX).
signal i32 Número do sinal que encerrou o processo; 0 quando signaled é false.

Command

O builder. Construído com Command::new, configurado com os métodos encadeados abaixo e depois executado. Os campos são públicos, mas normalmente você só os acessa por meio dos métodos.

pub struct Command {
    pub program: string,
    pub args: *Vector<string>,
    pub env_kv: *Vector<string>,   // entradas no formato "KEY=VAL"
    pub cwd_path: string,
    pub stdin_payload: string,
    pub flags: i32,                // bit 0 captura stdout, bit 1 captura stderr,
                                   // bit 2 env_clear, bit 3 pipe stdin,
                                   // bit 4 pipe stdout, bit 5 pipe stderr
}

Child

Handle para um processo que foi iniciado via spawn e ainda está em execução. Os handles de pipe são não-nulos apenas quando o builder pipe_* correspondente foi usado.

pub struct Child {
    pub pid: i32,
    pub stdin:  *ChildStdin,    // null a menos que pipe_stdin() tenha sido chamado
    pub stdout: *ChildStdout,   // null a menos que pipe_stdout() tenha sido chamado
    pub stderr: *ChildStderr,   // null a menos que pipe_stderr() tenha sido chamado
}

Structs de handle de pipe

Cada uma envolve um único handle do sistema operacional (HANDLE no Windows, fd no POSIX).

pub struct ChildStdin  { pub handle: i64 }
pub struct ChildStdout { pub handle: i64 }
pub struct ChildStderr { pub handle: i64 }

Construindo um comando

Todos os métodos do builder retornam *Command, o que permite encadeamento. Command::new aloca memória; libere-a com free() (idealmente via defer).

new / free

Função Assinatura Descrição
new fn new(program: string) -> *Command Cria um builder para program. Argumentos, variáveis de ambiente, diretório de trabalho e stdin herdam os do processo pai por padrão; a saída é encaminhada ao pai, a menos que um método capture_* seja chamado.
free fn free(self: *Command) Libera o builder (seus vetores args e env_kv e a struct). Use com defer.
let cmd: *Command = Command::new("echo");
defer cmd.free();
cmd.arg("hi").run();

Argumentos, variáveis de ambiente e diretório

Método Assinatura Descrição
arg fn arg(self: *Command, a: string) -> *Command Adiciona um argumento posicional.
args fn args(self: *Command, vs: *Vector<string>) -> *Command Adiciona cada string de vs como argumento (copia as entradas; a posse de vs continua com você).
env fn env(self: *Command, key: string, value: string) -> *Command Define uma variável de ambiente para o processo filho (sobrescreve o ambiente herdado). Armazenada internamente como "KEY=VAL".
env_clear fn env_clear(self: *Command) -> *Command Descarta o ambiente do processo pai; somente as variáveis definidas via env(k,v) chegam ao filho.
cwd fn cwd(self: *Command, path: string) -> *Command Define o diretório de trabalho do processo filho.
stdin_str fn stdin_str(self: *Command, s: string) -> *Command Envia s ao stdin do filho (somente com run()); o pipe é fechado após a escrita, sinalizando EOF.
let extras: *Vector<string> = Vector::new();
extras.push_all!("--verbose", "input.txt");

let cmd: *Command = Command::new("tool");
cmd.arg("build")
   .args(extras)
   .env_clear()
   .env("PATH", "/usr/bin:/bin")   // somente PATH chega ao filho agora
   .cwd("/tmp/repo")
   .stdin_str("data\n")
   .capture();

let r: !ProcessResult = cmd.run();
if r.ok { println!("exit:", r.val.exit_code); }
cmd.free();
extras.free();

Captura de saída

Esses métodos definem flags de captura consumidos por run(). Sem nenhum deles, stdout e stderr do processo filho herdam os do pai, e ProcessResult.stdout/.stderr serão "".

Método Assinatura Descrição
capture_out fn capture_out(self: *Command) -> *Command Captura stdout em ProcessResult.stdout (define o bit 0 do flag).
capture_err fn capture_err(self: *Command) -> *Command Captura stderr em ProcessResult.stderr (define o bit 1 do flag).
capture fn capture(self: *Command) -> *Command Captura ambos (equivalente a capture_out().capture_err(); define os bits 0+1).
let cmd: *Command = Command::new("wc");
cmd.arg("-l").stdin_str("a\nb\nc\n").capture_out().capture_err();
let r: !ProcessResult = cmd.run();
if r.ok {
    if r.val.exit_code == 0 {
        println!("lines:", r.val.stdout.trim());   // "3"
    } else {
        println!("failed:", r.val.stderr);
    }
}
cmd.free();

Capturar apenas stderr é o idioma para "executar um compilador/linter e exibir seus diagnósticos em caso de falha":

let cmd: *Command = Command::new("gcc");
cmd.arg("missing.c").capture_err();
let r: !ProcessResult = cmd.run();
if r.ok {
    if r.val.exit_code != 0 {
        println!("compile failed:", r.val.stderr.trim());
    } else {
        println!("ok");
    }
} else {
    println!("could not launch gcc:", r.err);
}
cmd.free();

Solicitação de pipes em tempo real

Esses métodos definem flags respeitados apenas por spawn(). Após o spawn(), o handle Child.stdin / .stdout / .stderr correspondente será não-nulo.

Método Assinatura Descrição
pipe_stdin fn pipe_stdin(self: *Command) -> *Command Abre um pipe de stdin em tempo real (bit 3 do flag); use child.stdin.write(s) / .close().
pipe_stdout fn pipe_stdout(self: *Command) -> *Command Abre um pipe de stdout em tempo real (bit 4 do flag); leia com child.stdout.read(n) / .read_all().
pipe_stderr fn pipe_stderr(self: *Command) -> *Command Abre um pipe de stderr em tempo real (bit 5 do flag); leia com child.stderr.read(n) / .read_all().

Execução

run

pub fn run(self: *Command) -> !ProcessResult

Executa de forma síncrona e bloqueia até o processo filho terminar. Respeita capture_*, stdin_str, env, env_clear, cwd. Retorna Err apenas em caso de falha no spawn; uma saída com código diferente de zero ainda é ok(...).

let r: !ProcessResult = Command::new("echo").arg("hi").capture_out().run();
if r.ok {
    println!("exit:", r.val.exit_code);
    println!("out:",  r.val.stdout.trim());   // "hi"
}

Como run() retorna !ProcessResult, você pode propagar falhas de spawn com ? e reservar Err para problemas genuínos de inicialização, mapeando uma saída com código não-zero para seu próprio erro:

fn capture_uname() -> !string {
    let cmd: *Command = Command::new("uname");
    defer cmd.free();
    let r: ProcessResult = cmd.arg("-s").capture_out().run()?;
    if r.exit_code != 0 { return err("uname failed"); }
    return ok(r.stdout.trim());
}

spawn

pub fn spawn(self: *Command) -> !Child

Inicia o processo sem aguardar e retorna um Child. stdin/stdout/stderr herdam os do processo pai a menos que um método pipe_* tenha sido chamado. Os flags capture_* e stdin_str são ignorados neste caminho — use os pipes em tempo real em seu lugar.

let r: !Child = Command::new("sleep").arg("60").spawn();
if r.ok {
    let ch: *Child = &r.val;
    println!("pid:", ch.pid);
    let k: ! = ch.kill(9);          // SIGKILL no POSIX, TerminateProcess no Windows
    if !k.ok { println!(k.err); }
    let res: !ProcessResult = ch.wait();
    if res.ok {
        if res.val.signaled {
            println!("killed by signal", res.val.signal);
        } else {
            println!("exited", res.val.exit_code);
        }
    }
}

Controlando um Child

wait / kill

Método Assinatura Descrição
wait fn wait(self: *Child) -> !ProcessResult Bloqueia até o processo filho terminar e coleta seu resultado. stdout/stderr são sempre vazios (spawn não captura).
kill fn kill(self: *Child, sig: i32) -> ! Envia o sinal sig. POSIX: kill(pid, sig). Windows: ignora sig e chama TerminateProcess. Retorna Err em caso de falha.
let r: !Child = Command::new("true").spawn();
if r.ok {
    let res: !ProcessResult = r.val.wait();
    if res.ok { println!("exited", res.val.exit_code); }
}

Pipes em streaming

Quando spawn() é chamado após pipe_stdin / pipe_stdout / pipe_stderr, o Child expõe handles de leitura/escrita em tempo real. Todos os métodos de leitura/escrita/fechamento retornam um Result e expõem erros do sistema operacional como err("...").

ChildStdin

Método Assinatura Descrição
write fn write(self: *ChildStdin, s: string) -> !i32 Escreve s no stdin do processo filho; retorna o número de bytes escritos.
close fn close(self: *ChildStdin) -> ! Fecha o pipe (envia EOF). Idempotente — define o handle como -1.

ChildStdout

Método Assinatura Descrição
read fn read(self: *ChildStdout, n: i32) -> !string Lê até n bytes; retorna "" em EOF.
read_all fn read_all(self: *ChildStdout) -> !string Lê tudo até EOF.
close fn close(self: *ChildStdout) -> ! Fecha o pipe de stdout.

ChildStderr

Método Assinatura Descrição
read fn read(self: *ChildStderr, n: i32) -> !string Lê até n bytes; retorna "" em EOF.
read_all fn read_all(self: *ChildStderr) -> !string Lê tudo que foi escrito no stderr até EOF.
close fn close(self: *ChildStderr) -> ! Fecha o pipe de stderr.

Um ciclo completo de ida e volta pelo cat — escreve a entrada, fecha stdin para EOF, drena stdout e então chama wait():

let cmd: *Command = Command::new("cat");
cmd.pipe_stdin().pipe_stdout();
let r: !Child = cmd.spawn();
if r.ok {
    let ch: *Child = &r.val;
    if ch.stdin != null {
        let w: !i32 = ch.stdin.write("hello\n");
        if w.ok { println!("wrote", w.val); }
        let c: ! = ch.stdin.close();        // EOF
        if !c.ok { println!(c.err); }
    }
    if ch.stdout != null {
        let all: !string = ch.stdout.read_all();
        if all.ok { print!(all.val); }
        let oc: ! = ch.stdout.close();
        if !oc.ok { println!(oc.err); }
    }
    let done: !ProcessResult = ch.wait();
    if done.ok { println!("exit", done.val.exit_code); }
}
cmd.free();

Para transmitir a saída em tempo real sem acumular tudo em buffer, faça um loop com read(n) limitado e pare quando retornar uma string vazia (EOF):

let r: !Child = Command::new("ls").arg("-la").pipe_stdout().spawn();
if r.ok {
    let ch: *Child = &r.val;
    if ch.stdout != null {
        while true {
            let chunk: !string = ch.stdout.read(4096);
            if !chunk.ok { println!(chunk.err); break; }
            if chunk.val.len() == 0 { break; }   // EOF
            print!(chunk.val);
        }
        let c: ! = ch.stdout.close();
        if !c.ok { println!(c.err); }
    }
    let done: !ProcessResult = ch.wait();
    if done.ok { println!("exit", done.val.exit_code); }
}

Funções auxiliares no nível do módulo

Atuam diretamente sobre um PID, sem envolver Command ou Child.

Função Assinatura Descrição
process_kill fn process_kill(pid: i32, sig: i32) -> ! Envia o sinal sig ao pid. POSIX: kill(pid, sig). Windows: TerminateProcess (sig é ignorado). Retorna Err em caso de falha.
process_exists fn process_exists(pid: i32) -> bool true se um processo ativo possui este PID. POSIX: kill(pid, 0). Windows: OpenProcess + GetExitCodeProcess(STILL_ACTIVE).
if process_exists(1234) {
    let r: ! = process_kill(1234, 15);   // SIGTERM
    if !r.ok { println!(r.err); }
} else {
    println!("pid 1234 not running");
}
  • Sem shell. Command::new("ls -la") procura por um programa literalmente chamado
  • ls -la. Passe o programa e cada argumento separadamente: Command::new("ls").arg("-la"). Para usar recursos do shell, invoque-o explicitamente:

  let cmd: *Command = Command::new("sh");
  cmd.arg("-c").arg("ls -la | wc -l").capture_out();
  let r: !ProcessResult = cmd.run();
  if r.ok && r.val.exit_code == 0 {
      println!("entries:", r.val.stdout.trim());
  }
  cmd.free();
  ```

- **Streaming e `run()` não se misturam.** `run()` ignora `pipe_*`; `spawn()` ignora
  `capture_*` e `stdin_str`. Escolha um modelo por comando.
- **Sinais são moldados pelo POSIX.** `signaled` / `signal` só têm significado no POSIX;
  no Windows, `kill` sempre força o encerramento independentemente do valor de `sig`.
- **Código de saída `127`** retornado por um `run()` que deu `ok` geralmente significa
  que o programa não foi encontrado ou que o `chdir` para o `cwd` falhou no filho
  (uma falha de `execvp` / `chdir` dentro do processo filho faz com que ele chame
  `_exit(127)`).
- **Libere o builder.** `Command::new` aloca com `malloc`; sempre chame `free()`
  (use `defer cmd.free();` logo após a construção) para liberar a struct e seus vetores
  internos.

## Veja também

- [`stdlib::os`](09-os.md) — primitivas de nível mais baixo do sistema operacional (`os_shell` e afins).
- [`stdlib::env`](10-env.md) — leitura e inspeção das variáveis de ambiente do processo pai.
- [`fs-io`](08-fs-io.md) — `eprintln` e auxiliares de stream para registrar a saída capturada.