Como Manipular Caminhos (Paths) em Zig

Introdução

Manipular caminhos de arquivo (paths) é essencial para qualquer programa que trabalhe com o sistema de arquivos. Zig oferece std.fs.path com funções para juntar, separar, normalizar e extrair componentes de caminhos de forma portável entre sistemas operacionais.

Nesta receita, você aprenderá as operações mais comuns com caminhos.

Pré-requisitos

Juntar Componentes de Caminho

const std = @import("std");
const path = std.fs.path;

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

    // Juntar componentes de caminho
    const resultado = try path.join(allocator, &[_][]const u8{
        "/home",
        "usuario",
        "projetos",
        "meu-app",
        "src",
        "main.zig",
    });
    defer allocator.free(resultado);

    std.debug.print("Caminho: {s}\n", .{resultado});

    // Juntar com diretório relativo
    const relativo = try path.join(allocator, &[_][]const u8{
        "src",
        "modules",
        "parser.zig",
    });
    defer allocator.free(relativo);

    std.debug.print("Relativo: {s}\n", .{relativo});
}

Extrair Componentes de um Caminho

const std = @import("std");
const path = std.fs.path;

pub fn main() !void {
    const caminho = "/home/usuario/projeto/src/main.zig";

    // Nome do diretório (dirname)
    const dir = path.dirname(caminho);
    std.debug.print("Diretório: {s}\n", .{dir orelse "(vazio)"});

    // Nome do arquivo (basename)
    const base = path.basename(caminho);
    std.debug.print("Arquivo: {s}\n", .{base});

    // Extensão
    const ext = path.extension(caminho);
    std.debug.print("Extensão: {s}\n", .{ext});

    // Stem (nome sem extensão)
    const stem = path.stem(caminho);
    std.debug.print("Stem: {s}\n", .{stem});

    // Verificar se é absoluto
    std.debug.print("É absoluto? {}\n", .{path.isAbsolute(caminho)});

    // Componentes separados
    std.debug.print("\nComponentes:\n", .{});
    var it = std.mem.splitScalar(u8, caminho, '/');
    while (it.next()) |componente| {
        if (componente.len > 0) {
            std.debug.print("  {s}\n", .{componente});
        }
    }
}

Saída esperada

Diretório: /home/usuario/projeto/src
Arquivo: main.zig
Extensão: .zig
Stem: main
É absoluto? true

Componentes:
  home
  usuario
  projeto
  src
  main.zig

Trocar Extensão de Arquivo

const std = @import("std");
const path = std.fs.path;

fn trocarExtensao(allocator: std.mem.Allocator, caminho: []const u8, nova_ext: []const u8) ![]u8 {
    const dir = path.dirname(caminho);
    const stem_name = path.stem(caminho);

    if (dir) |d| {
        return std.fmt.allocPrint(allocator, "{s}/{s}{s}", .{ d, stem_name, nova_ext });
    } else {
        return std.fmt.allocPrint(allocator, "{s}{s}", .{ stem_name, nova_ext });
    }
}

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

    const arquivos = [_][]const u8{
        "projeto/main.zig",
        "dados/config.json",
        "src/modulo.zig",
        "relatorio.txt",
    };

    for (&arquivos) |arq| {
        const novo = try trocarExtensao(allocator, arq, ".bak");
        defer allocator.free(novo);
        std.debug.print("{s} -> {s}\n", .{ arq, novo });
    }
}

Resolver Caminho Absoluto

const std = @import("std");

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

    // Obter diretório de trabalho atual
    const cwd = try std.fs.cwd().realpathAlloc(allocator, ".");
    defer allocator.free(cwd);

    std.debug.print("CWD: {s}\n", .{cwd});

    // Construir caminho absoluto
    const absoluto = try std.fs.path.join(allocator, &[_][]const u8{ cwd, "src", "main.zig" });
    defer allocator.free(absoluto);

    std.debug.print("Absoluto: {s}\n", .{absoluto});
}

Exemplo Prático: Organizar Arquivos por Extensão

const std = @import("std");
const path = std.fs.path;

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

    const arquivos = [_][]const u8{
        "foto1.jpg",
        "documento.pdf",
        "main.zig",
        "foto2.png",
        "notas.txt",
        "build.zig",
        "relatorio.pdf",
        "avatar.jpg",
    };

    // Agrupar por extensão
    var grupos = std.StringHashMap(std.ArrayList([]const u8)).init(allocator);
    defer {
        var it = grupos.valueIterator();
        while (it.next()) |lista| lista.deinit();
        grupos.deinit();
    }

    for (&arquivos) |arq| {
        const ext = path.extension(arq);
        const result = try grupos.getOrPut(ext);
        if (!result.found_existing) {
            result.value_ptr.* = std.ArrayList([]const u8).init(allocator);
        }
        try result.value_ptr.append(arq);
    }

    // Exibir grupos
    var it = grupos.iterator();
    while (it.next()) |entry| {
        std.debug.print("\n{s}:\n", .{entry.key_ptr.*});
        for (entry.value_ptr.items) |arq| {
            std.debug.print("  {s}\n", .{arq});
        }
    }
}

Dicas e Boas Práticas

  1. Use std.fs.path.join: Nunca concatene paths manualmente com / – use join para portabilidade.

  2. dirname retorna optional: Pode ser null para caminhos sem diretório.

  3. A extensão inclui o ponto: extension("main.zig") retorna ".zig", não "zig".

  4. Normalize caminhos: Use path.join para remover componentes redundantes como ./ e ../.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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