Como Executar Processos Externos em Zig

Introdução

Executar processos externos permite que seu programa Zig interaja com outras ferramentas do sistema, execute comandos shell, compile código e automatize tarefas. A API std.process.Child oferece controle completo sobre a execução de processos filhos.

Nesta receita, você aprenderá a executar comandos, capturar saída e verificar o resultado.

Pré-requisitos

Executar Comando Simples

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Executar 'ls -la' e capturar a saída
    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = &[_][]const u8{ "ls", "-la" },
    });
    defer {
        allocator.free(result.stdout);
        allocator.free(result.stderr);
    }

    std.debug.print("Código de saída: {}\n", .{result.term});
    std.debug.print("Saída:\n{s}\n", .{result.stdout});

    if (result.stderr.len > 0) {
        std.debug.print("Erros:\n{s}\n", .{result.stderr});
    }
}

Executar com Argumentos Dinâmicos

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Construir argumentos dinamicamente
    var args = std.ArrayList([]const u8).init(allocator);
    defer args.deinit();

    try args.append("echo");
    try args.append("Olá");
    try args.append("do");
    try args.append("Zig!");

    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = args.items,
    });
    defer {
        allocator.free(result.stdout);
        allocator.free(result.stderr);
    }

    std.debug.print("Saída: {s}", .{result.stdout});
}

Verificar Código de Retorno

const std = @import("std");

fn executarComando(allocator: std.mem.Allocator, argv: []const []const u8) !bool {
    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = argv,
    });
    defer {
        allocator.free(result.stdout);
        allocator.free(result.stderr);
    }

    switch (result.term) {
        .exited => |code| {
            if (code == 0) {
                std.debug.print("Comando executado com sucesso\n", .{});
                return true;
            } else {
                std.debug.print("Comando falhou com código {d}\n", .{code});
                if (result.stderr.len > 0) {
                    std.debug.print("Erro: {s}\n", .{result.stderr});
                }
                return false;
            }
        },
        .signal => |sig| {
            std.debug.print("Processo terminado por sinal {d}\n", .{sig});
            return false;
        },
        else => {
            std.debug.print("Término inesperado\n", .{});
            return false;
        },
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Comando que deve funcionar
    _ = try executarComando(allocator, &[_][]const u8{ "echo", "teste" });

    // Comando que pode falhar
    _ = try executarComando(allocator, &[_][]const u8{ "ls", "/caminho/que/nao/existe" });
}

Executar com Diretório de Trabalho

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Executar 'pwd' em diretório específico
    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = &[_][]const u8{"pwd"},
        .cwd = "/tmp",
    });
    defer {
        allocator.free(result.stdout);
        allocator.free(result.stderr);
    }

    std.debug.print("Diretório de trabalho: {s}", .{result.stdout});
}

Capturar Saída Linha por Linha

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const result = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = &[_][]const u8{ "ls", "-1", "/tmp" },
    });
    defer {
        allocator.free(result.stdout);
        allocator.free(result.stderr);
    }

    // Processar saída linha por linha
    var linhas = std.mem.splitScalar(u8, result.stdout, '\n');
    var count: usize = 0;
    std.debug.print("Arquivos em /tmp:\n", .{});
    while (linhas.next()) |linha| {
        if (linha.len == 0) continue;
        count += 1;
        if (count <= 10) {
            std.debug.print("  {d}. {s}\n", .{ count, linha });
        }
    }
    if (count > 10) {
        std.debug.print("  ... e mais {d} arquivos\n", .{count - 10});
    }
    std.debug.print("Total: {d} arquivos\n", .{count});
}

Dicas e Boas Práticas

  1. Sempre libere stdout e stderr: Use defer allocator.free() para ambos.

  2. Verifique o código de saída: Nem todo comando retorna 0 para sucesso.

  3. Evite shell injection: Passe argumentos como array, não como string concatenada.

  4. Limite o tamanho da saída: Use max_output_bytes para evitar consumir memória demais.

  5. Trate sinais: Processos podem ser terminados por sinais, não apenas por exit codes.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.