Como Ler Argumentos de Linha de Comando em Zig

Introdução

Ler argumentos de linha de comando é fundamental para criar ferramentas CLI, scripts e programas configuráveis. Zig oferece acesso direto aos argumentos via std.process.args() e std.process.argsWithAllocator().

Nesta receita, você aprenderá a processar argumentos de diferentes formas.

Pré-requisitos

Ler Argumentos Básicos

const std = @import("std");

pub fn main() !void {
    // args() retorna um iterador sobre os argumentos
    var args = std.process.args();

    // O primeiro argumento é sempre o nome do programa
    const programa = args.next().?;
    std.debug.print("Programa: {s}\n", .{programa});

    // Iterar sobre os demais argumentos
    std.debug.print("Argumentos:\n", .{});
    var i: usize = 1;
    while (args.next()) |arg| {
        std.debug.print("  [{d}] {s}\n", .{ i, arg });
        i += 1;
    }

    if (i == 1) {
        std.debug.print("  (nenhum argumento)\n", .{});
    }
}

Execute com: zig run main.zig -- argumento1 argumento2 argumento3

Coletar Argumentos em Array

const std = @import("std");

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

    // Coletar todos os argumentos em um slice
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    std.debug.print("Total de argumentos: {d}\n", .{args.len});

    for (args, 0..) |arg, i| {
        std.debug.print("  args[{d}] = \"{s}\"\n", .{ i, arg });
    }
}

Parsear Flags e Opções

const std = @import("std");

const Config = struct {
    verbose: bool = false,
    output: ?[]const u8 = null,
    count: u32 = 1,
    arquivos: std.ArrayList([]const u8),

    pub fn init(allocator: std.mem.Allocator) Config {
        return .{
            .arquivos = std.ArrayList([]const u8).init(allocator),
        };
    }

    pub fn deinit(self: *Config) void {
        self.arquivos.deinit();
    }
};

fn parsearArgs(allocator: std.mem.Allocator) !Config {
    var config = Config.init(allocator);
    errdefer config.deinit();

    var args = std.process.args();
    _ = args.next(); // Pular nome do programa

    while (args.next()) |arg| {
        if (std.mem.eql(u8, arg, "-v") or std.mem.eql(u8, arg, "--verbose")) {
            config.verbose = true;
        } else if (std.mem.eql(u8, arg, "-o") or std.mem.eql(u8, arg, "--output")) {
            config.output = args.next() orelse {
                std.debug.print("Erro: -o requer um valor\n", .{});
                return error.InvalidArgument;
            };
        } else if (std.mem.eql(u8, arg, "-n") or std.mem.eql(u8, arg, "--count")) {
            const val_str = args.next() orelse {
                std.debug.print("Erro: -n requer um número\n", .{});
                return error.InvalidArgument;
            };
            config.count = std.fmt.parseInt(u32, val_str, 10) catch {
                std.debug.print("Erro: '{s}' não é um número válido\n", .{val_str});
                return error.InvalidArgument;
            };
        } else if (std.mem.startsWith(u8, arg, "-")) {
            std.debug.print("Opção desconhecida: {s}\n", .{arg});
            return error.InvalidArgument;
        } else {
            try config.arquivos.append(arg);
        }
    }

    return config;
}

fn mostrarUso() void {
    std.debug.print(
        \\Uso: programa [opções] [arquivos...]
        \\
        \\Opções:
        \\  -v, --verbose    Modo verboso
        \\  -o, --output     Arquivo de saída
        \\  -n, --count      Número de repetições
        \\
        \\
    , .{});
}

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

    var config = parsearArgs(allocator) catch {
        mostrarUso();
        return;
    };
    defer config.deinit();

    std.debug.print("Configuração:\n", .{});
    std.debug.print("  Verbose: {}\n", .{config.verbose});
    std.debug.print("  Output: {s}\n", .{config.output orelse "(padrão)"});
    std.debug.print("  Count: {d}\n", .{config.count});
    std.debug.print("  Arquivos: {d}\n", .{config.arquivos.items.len});
    for (config.arquivos.items) |arq| {
        std.debug.print("    - {s}\n", .{arq});
    }
}

Subcomandos Estilo Git

const std = @import("std");

fn cmdInit() void {
    std.debug.print("Inicializando projeto...\n", .{});
}

fn cmdBuild(args: *std.process.ArgIterator) void {
    var release = false;
    while (args.next()) |arg| {
        if (std.mem.eql(u8, arg, "--release")) {
            release = true;
        }
    }
    std.debug.print("Compilando em modo {s}...\n", .{if (release) "release" else "debug"});
}

fn cmdHelp() void {
    std.debug.print(
        \\Comandos disponíveis:
        \\  init     Inicializar projeto
        \\  build    Compilar projeto [--release]
        \\  help     Mostrar esta ajuda
        \\
    , .{});
}

pub fn main() !void {
    var args = std.process.args();
    _ = args.next(); // nome do programa

    const subcomando = args.next() orelse {
        std.debug.print("Erro: nenhum comando fornecido\n\n", .{});
        cmdHelp();
        return;
    };

    if (std.mem.eql(u8, subcomando, "init")) {
        cmdInit();
    } else if (std.mem.eql(u8, subcomando, "build")) {
        cmdBuild(&args);
    } else if (std.mem.eql(u8, subcomando, "help")) {
        cmdHelp();
    } else {
        std.debug.print("Comando desconhecido: {s}\n\n", .{subcomando});
        cmdHelp();
    }
}

Dicas e Boas Práticas

  1. O primeiro argumento é o programa: Sempre pule args[0] ao processar argumentos do usuário.

  2. Valide sempre: Verifique se flags com valor realmente receberam o valor esperado.

  3. Mostre uso em caso de erro: Sempre ofereça uma mensagem de ajuda clara.

  4. Suporte formas curtas e longas: -v e --verbose para melhor usabilidade.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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