Como Ler Arquivo Linha por Linha em Zig

Como Ler Arquivo Linha por Linha em Zig

Ler um arquivo linha por linha é essencial quando você precisa processar arquivos grandes sem carregar tudo na memória, ou quando quer processar cada linha individualmente (como em logs, CSV ou configurações).

Leitura Básica Linha por Linha

Use reader().readUntilDelimiterOrEof para ler uma linha por vez.

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
    defer arquivo.close();

    // Criar buffered reader para melhor desempenho
    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var linha_buf: [4096]u8 = undefined;
    var numero_linha: u32 = 0;

    while (reader.readUntilDelimiterOrEof(&linha_buf, '\n')) |linha_opt| {
        const linha = linha_opt orelse break;
        numero_linha += 1;
        // Remover \r se existir (Windows line endings)
        const limpa = std.mem.trimRight(u8, linha, "\r");
        try stdout.print("{d:>4}: {s}\n", .{ numero_linha, limpa });
    } else |err| {
        return err;
    }

    try stdout.print("\nTotal: {d} linhas\n", .{numero_linha});
}

Leitura com Alocação Dinâmica por Linha

Para linhas de tamanho arbitrário, use readUntilDelimiterAlloc.

const std = @import("std");

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

    const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
    defer arquivo.close();

    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var numero_linha: u32 = 0;
    while (true) {
        const linha = reader.readUntilDelimiterAlloc(allocator, '\n', 1024 * 1024) catch |err| {
            if (err == error.EndOfStream) break;
            return err;
        };
        defer allocator.free(linha);

        numero_linha += 1;
        const limpa = std.mem.trimRight(u8, linha, "\r");
        try stdout.print("{d}: {s}\n", .{ numero_linha, limpa });
    }
}

Coletar Todas as Linhas

Armazene todas as linhas em um ArrayList para processamento posterior.

const std = @import("std");

fn lerTodasLinhas(allocator: std.mem.Allocator, caminho: []const u8) !std.ArrayList([]u8) {
    const arquivo = try std.fs.cwd().openFile(caminho, .{});
    defer arquivo.close();

    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var linhas = std.ArrayList([]u8).init(allocator);
    errdefer {
        for (linhas.items) |linha| allocator.free(linha);
        linhas.deinit();
    }

    while (true) {
        const linha = reader.readUntilDelimiterAlloc(allocator, '\n', 1024 * 1024) catch |err| {
            if (err == error.EndOfStream) break;
            return err;
        };
        const limpa = std.mem.trimRight(u8, linha, "\r");
        if (limpa.len < linha.len) {
            // Realocar sem o \r
            const nova = try allocator.dupe(u8, limpa);
            allocator.free(linha);
            try linhas.append(nova);
        } else {
            try linhas.append(linha);
        }
    }

    return linhas;
}

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

    var linhas = try lerTodasLinhas(allocator, "dados.txt");
    defer {
        for (linhas.items) |linha| allocator.free(linha);
        linhas.deinit();
    }

    try stdout.print("Total de linhas: {d}\n", .{linhas.items.len});
    for (linhas.items, 0..) |linha, i| {
        try stdout.print("  [{d}] \"{s}\"\n", .{ i, linha });
    }
}

Processar Linhas com Filtro

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    const arquivo = try std.fs.cwd().openFile("log.txt", .{});
    defer arquivo.close();

    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var linha_buf: [8192]u8 = undefined;
    var erros: u32 = 0;
    var avisos: u32 = 0;

    while (reader.readUntilDelimiterOrEof(&linha_buf, '\n')) |linha_opt| {
        const linha = linha_opt orelse break;

        if (std.mem.indexOf(u8, linha, "[ERROR]") != null) {
            erros += 1;
            try stdout.print("ERRO: {s}\n", .{linha});
        } else if (std.mem.indexOf(u8, linha, "[WARN]") != null) {
            avisos += 1;
        }
    } else |err| {
        return err;
    }

    try stdout.print("\nResumo: {d} erros, {d} avisos\n", .{ erros, avisos });
}

Exemplo Prático: Parser de Arquivo de Configuração

const std = @import("std");

const Config = struct {
    entries: std.StringHashMap([]const u8),
    allocator: std.mem.Allocator,

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

    fn deinit(self: *Config) void {
        var it = self.entries.iterator();
        while (it.next()) |entry| {
            self.allocator.free(entry.key_ptr.*);
            self.allocator.free(entry.value_ptr.*);
        }
        self.entries.deinit();
    }

    fn get(self: *const Config, chave: []const u8) ?[]const u8 {
        return self.entries.get(chave);
    }
};

fn carregarConfig(allocator: std.mem.Allocator, caminho: []const u8) !Config {
    const arquivo = try std.fs.cwd().openFile(caminho, .{});
    defer arquivo.close();

    var buf_reader = std.io.bufferedReader(arquivo.reader());
    const reader = buf_reader.reader();

    var config = Config.init(allocator);
    errdefer config.deinit();

    var linha_buf: [4096]u8 = undefined;
    while (reader.readUntilDelimiterOrEof(&linha_buf, '\n')) |linha_opt| {
        const linha_raw = linha_opt orelse break;
        const linha = std.mem.trim(u8, std.mem.trimRight(u8, linha_raw, "\r"), " \t");

        // Ignorar linhas vazias e comentários
        if (linha.len == 0 or linha[0] == '#' or linha[0] == ';') continue;

        // Buscar separador '='
        const sep = std.mem.indexOf(u8, linha, "=") orelse continue;

        const chave = std.mem.trim(u8, linha[0..sep], " \t");
        const valor = std.mem.trim(u8, linha[sep + 1 ..], " \t");

        const chave_dup = try allocator.dupe(u8, chave);
        const valor_dup = try allocator.dupe(u8, valor);
        try config.entries.put(chave_dup, valor_dup);
    } else |err| {
        return err;
    }

    return config;
}

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

    var config = try carregarConfig(allocator, "app.conf");
    defer config.deinit();

    // Acessar valores
    if (config.get("host")) |host| {
        try stdout.print("Host: {s}\n", .{host});
    }
    if (config.get("porta")) |porta| {
        try stdout.print("Porta: {s}\n", .{porta});
    }
}

Veja Também

Continue aprendendo Zig

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