---
title: "Contador de Palavras em Zig — Tutorial Passo a Passo"
url: "https://ziglang.com.br/projetos/contador-de-palavras-em-zig-tutorial-passo-a-passo/"
markdown_url: "https://ziglang.com.br/projetos/contador-de-palavras-em-zig-tutorial-passo-a-passo.MD"
description: "Construa um contador de palavras estilo wc em Zig que conta linhas, palavras e caracteres de arquivos com suporte a múltiplos arquivos."
date: "2026-02-21"
author: "Zig Brasil"
---

# Contador de Palavras em Zig — Tutorial Passo a Passo

Construa um contador de palavras estilo wc em Zig que conta linhas, palavras e caracteres de arquivos com suporte a múltiplos arquivos.


# Contador de Palavras em Zig — Tutorial Passo a Passo

Neste tutorial, vamos construir um **contador de palavras** inspirado no utilitário Unix `wc`. Este projeto é ideal para aprender sobre leitura de arquivos, processamento de streams e argumentos de linha de comando em Zig.

## O Que Vamos Construir

Nosso contador vai:

- Contar linhas, palavras e caracteres de arquivos
- Aceitar múltiplos arquivos como argumento
- Ler da entrada padrão (stdin) quando nenhum arquivo for passado
- Exibir totais quando múltiplos arquivos forem processados
- Suportar flags: `-l` (linhas), `-w` (palavras), `-c` (caracteres)

## Por Que Este Projeto?

O `wc` é um utilitário Unix clássico que ilustra perfeitamente o processamento de streams — lemos dados byte a byte e mantemos contadores. Em Zig, isso nos permite explorar a API de I/O buffered, argumentos de processo e o padrão de processar arquivos como streams.

## Passo 1: Estrutura de Contagem

```zig
const std = @import("std");
const fs = std.fs;
const io = std.io;
const mem = std.mem;
const process = std.process;

/// Resultado da contagem para um arquivo.
/// Struct simples e imutável — representa um valor, não um estado.
const Contagem = struct {
    linhas: usize = 0,
    palavras: usize = 0,
    bytes: usize = 0,
    nome: []const u8 = "",

    /// Soma duas contagens (útil para o total).
    pub fn somar(a: Contagem, b: Contagem) Contagem {
        return .{
            .linhas = a.linhas + b.linhas,
            .palavras = a.palavras + b.palavras,
            .bytes = a.bytes + b.bytes,
            .nome = "total",
        };
    }
};

/// Flags de exibição.
const Flags = struct {
    linhas: bool = false,
    palavras: bool = false,
    bytes: bool = false,

    /// Se nenhuma flag foi especificada, mostra tudo.
    pub fn mostrarTudo(self: Flags) bool {
        return !self.linhas and !self.palavras and !self.bytes;
    }
};
```

## Passo 2: Algoritmo de Contagem

```zig
/// Conta linhas, palavras e bytes de um reader genérico.
///
/// Decisão de design: a função aceita anytype (reader genérico)
/// em vez de um tipo específico de arquivo. Isso permite usá-la
/// tanto com arquivos quanto com stdin, sem duplicar código.
/// É o equivalente Zig de uma interface/trait — duck typing em
/// tempo de compilação.
fn contar(reader: anytype) Contagem {
    var resultado = Contagem{};
    var em_palavra = false;

    // Processamos byte a byte. Para arquivos muito grandes,
    // isso é eficiente porque o reader já faz buffering interno.
    while (reader.readByte()) |byte| {
        resultado.bytes += 1;

        if (byte == '\n') {
            resultado.linhas += 1;
        }

        const eh_espaco = switch (byte) {
            ' ', '\t', '\n', '\r' => true,
            else => false,
        };

        if (eh_espaco) {
            em_palavra = false;
        } else if (!em_palavra) {
            em_palavra = true;
            resultado.palavras += 1;
        }
    } else |_| {} // EOF ou erro — terminamos

    return resultado;
}

/// Conta palavras de um arquivo específico.
fn contarArquivo(caminho: []const u8) !Contagem {
    const arquivo = try fs.cwd().openFile(caminho, .{});
    defer arquivo.close();

    var buf_reader = io.bufferedReader(arquivo.reader());
    var resultado = contar(buf_reader.reader());
    resultado.nome = caminho;
    return resultado;
}
```

**Por que `anytype`?** Em linguagens OOP, usaríamos uma interface `Reader`. Em Zig, `anytype` com duck typing em comptime serve o mesmo propósito: qualquer tipo que tenha um método `readByte() !u8` funciona. O compilador gera código especializado para cada tipo concreto, sem overhead de vtable. Veja mais em [generics em Zig](/tutoriais/generics/).

## Passo 3: Formatação de Saída

```zig
/// Exibe uma contagem formatada.
fn exibirContagem(writer: anytype, contagem: Contagem, flags: Flags) !void {
    const tudo = flags.mostrarTudo();

    if (tudo or flags.linhas) {
        try writer.print("{d:>8}", .{contagem.linhas});
    }
    if (tudo or flags.palavras) {
        try writer.print("{d:>8}", .{contagem.palavras});
    }
    if (tudo or flags.bytes) {
        try writer.print("{d:>8}", .{contagem.bytes});
    }

    if (contagem.nome.len > 0) {
        try writer.print(" {s}", .{contagem.nome});
    }
    try writer.print("\n", .{});
}
```

## Passo 4: Parsing de Argumentos

```zig
/// Processa argumentos da linha de comando.
/// Retorna as flags e a lista de arquivos.
fn processarArgumentos(allocator: std.mem.Allocator) !struct {
    flags: Flags,
    arquivos: std.ArrayList([]const u8),
} {
    var flags = Flags{};
    var arquivos = std.ArrayList([]const u8).init(allocator);

    var args = try process.argsWithAllocator(allocator);
    defer args.deinit();

    _ = args.next(); // Pula o nome do programa

    while (args.next()) |arg| {
        if (arg.len > 0 and arg[0] == '-') {
            // Parse de flags
            for (arg[1..]) |c| {
                switch (c) {
                    'l' => flags.linhas = true,
                    'w' => flags.palavras = true,
                    'c' => flags.bytes = true,
                    else => {
                        std.debug.print("Flag desconhecida: -{c}\n", .{c});
                    },
                }
            }
        } else {
            try arquivos.append(arg);
        }
    }

    return .{ .flags = flags, .arquivos = arquivos };
}
```

## Passo 5: Função Principal

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

    const stdout = io.getStdOut().writer();

    var resultado = try processarArgumentos(allocator);
    defer resultado.arquivos.deinit();

    const flags = resultado.flags;
    const arquivos = resultado.arquivos.items;

    if (arquivos.len == 0) {
        // Sem arquivos — lê da stdin
        var buf_reader = io.bufferedReader(io.getStdIn().reader());
        const contagem = contar(buf_reader.reader());
        try exibirContagem(stdout, contagem, flags);
        return;
    }

    var total = Contagem{};

    for (arquivos) |caminho| {
        const contagem = contarArquivo(caminho) catch |err| {
            try stdout.print("wc: {s}: {}\n", .{ caminho, err });
            continue;
        };
        try exibirContagem(stdout, contagem, flags);
        total = total.somar(contagem);
    }

    // Exibe total se houver mais de um arquivo
    if (arquivos.len > 1) {
        try exibirContagem(stdout, total, flags);
    }
}
```

## Passo 6: Testes

```zig
test "contar texto simples" {
    const texto = "Olá mundo\nSegunda linha\n";
    var stream = std.io.fixedBufferStream(texto);
    const resultado = contar(stream.reader());

    try std.testing.expectEqual(@as(usize, 2), resultado.linhas);
    try std.testing.expectEqual(@as(usize, 4), resultado.palavras);
    try std.testing.expectEqual(@as(usize, texto.len), resultado.bytes);
}

test "contar texto vazio" {
    const texto = "";
    var stream = std.io.fixedBufferStream(texto);
    const resultado = contar(stream.reader());

    try std.testing.expectEqual(@as(usize, 0), resultado.linhas);
    try std.testing.expectEqual(@as(usize, 0), resultado.palavras);
}

test "contar múltiplos espaços" {
    const texto = "a  b   c\n";
    var stream = std.io.fixedBufferStream(texto);
    const resultado = contar(stream.reader());

    try std.testing.expectEqual(@as(usize, 3), resultado.palavras);
}

test "somar contagens" {
    const a = Contagem{ .linhas = 10, .palavras = 50, .bytes = 200 };
    const b = Contagem{ .linhas = 5, .palavras = 30, .bytes = 100 };
    const total = a.somar(b);

    try std.testing.expectEqual(@as(usize, 15), total.linhas);
    try std.testing.expectEqual(@as(usize, 80), total.palavras);
    try std.testing.expectEqual(@as(usize, 300), total.bytes);
}
```

## Compilando e Executando

```bash
zig build

# Contar palavras de um arquivo
./zig-out/bin/contador-palavras arquivo.txt

# Contar apenas linhas
./zig-out/bin/contador-palavras -l arquivo.txt

# Múltiplos arquivos
./zig-out/bin/contador-palavras arquivo1.txt arquivo2.txt

# Ler da stdin
echo "Olá mundo" | ./zig-out/bin/contador-palavras
```

## Conceitos Aprendidos

- Leitura de arquivos com `std.fs` e buffered readers
- Processamento de streams byte a byte
- Argumentos de linha de comando com `std.process`
- Generics com `anytype` para duck typing
- Composição de resultados com funções puras

## Próximos Passos

- Aprenda sobre [I/O de arquivos](/receitas/io-arquivos/) para operações avançadas
- Explore [argumentos CLI](/receitas/args-cli/) para parsing mais sofisticado
- Construa o próximo projeto: [Validador de CPF](/projetos/validador-cpf/)
