Gerando relatórios com Bash e awk

1. Introdução ao awk e sua integração com Bash

awk é uma linguagem de programação criada na década de 1970 por Aho, Weinberger e Kernighan, projetada especificamente para processamento de texto e geração de relatórios. Sua sintaxe enxuta e poderosa permite extrair, transformar e formatar dados de arquivos estruturados como logs, CSVs e tabelas.

A integração entre Bash e awk é natural: enquanto o Bash gerencia fluxos de execução, arquivos e chamadas de sistema, o awk faz o trabalho pesado de análise textual. Um comando awk típico segue a estrutura:

awk 'padrão { ação }' arquivo

O padrão define quais linhas processar (ou é omitido para processar todas), e a ação determina o que fazer com cada linha correspondente. Essa simplicidade esconde um motor de processamento extremamente eficiente.

2. Fundamentos do awk para relatórios

No awk, cada linha de entrada é um registro e cada palavra ou coluna é um campo. Os separadores padrão são espaços ou tabulações, mas podem ser customizados.

Variáveis internas essenciais:

  • NR — número do registro atual (linha)
  • NF — número de campos no registro atual
  • FNR — número do registro no arquivo atual (útil com múltiplos arquivos)
  • FS — separador de campos (padrão: espaço)
  • OFS — separador de campos na saída
  • RS — separador de registros (padrão: newline)
  • ORS — separador de registros na saída

Exemplo básico de leitura de um log:

# log.txt
2025-03-01 10:15:30 ERRO conexão recusada
2025-03-01 10:16:45 INFO requisição bem-sucedida
2025-03-01 10:17:10 ERRO timeout

awk '{ print NR, $1, $3, $4 }' log.txt

Saída:

1 2025-03-01 ERRO conexão
2 2025-03-01 INFO requisição
3 2025-03-01 ERRO timeout

3. Formatação de saída com printf

Para relatórios profissionais, o printf do awk oferece controle preciso sobre alinhamento, largura e justificação:

awk '{
    printf "%-15s %-10s %-8s %s\n", $1, $2, $3, $4
}' log.txt

Onde %-15s significa: string justificada à esquerda com 15 caracteres de largura. Para números, usamos %d (inteiro) ou %.2f (float com 2 casas decimais).

Criando um cabeçalho formatado:

awk 'BEGIN {
    printf "%-15s %-10s %-8s %-20s\n", "DATA", "HORA", "NIVEL", "MENSAGEM"
    printf "%-15s %-10s %-8s %-20s\n", "---------------", "----------", "--------", "--------------------"
}
{ printf "%-15s %-10s %-8s %-20s\n", $1, $2, $3, $4 }' log.txt

4. Agregação e sumarização de dados

Arrays associativos no awk permitem acumular valores por chave. Exemplo de relatório de vendas:

# vendas.txt
ProdutoA 150.00
ProdutoB 230.50
ProdutoA 89.90
ProdutoC 450.00
ProdutoB 120.00

awk '{
    total[$1] += $2
    contagem[$1]++
}
END {
    printf "%-12s %-8s %-10s\n", "PRODUTO", "QTDE", "TOTAL"
    printf "%-12s %-8s %-10s\n", "------------", "--------", "----------"
    for (produto in total) {
        media = total[produto] / contagem[produto]
        printf "%-12s %-8d R$ %7.2f\n", produto, contagem[produto], total[produto]
    }
}' vendas.txt

Saída:

PRODUTO      QTDE     TOTAL     
------------ -------- ----------
ProdutoA     2        R$  239.90
ProdutoB     2        R$  350.50
ProdutoC     1        R$  450.00

5. Relatórios condicionais e filtragem avançada

Padrões compostos com operadores lógicos permitem filtrar dados com precisão:

# log_servidor.txt
2025-03-01 10:15:30 ERRO 500 conexão recusada
2025-03-01 10:16:45 INFO 200 requisição ok
2025-03-01 10:17:10 ERRO 503 timeout
2025-03-01 10:18:00 INFO 200 requisição ok
2025-03-01 10:19:30 ERRO 500 falha interna

awk '$3 == "ERRO" && $4 >= 500 {
    erros[$4]++
}
END {
    print "Relatório de Erros (HTTP >= 500)"
    for (codigo in erros) {
        printf "Código %d: %d ocorrências\n", codigo, erros[codigo]
    }
}' log_servidor.txt

Para filtrar por intervalo de datas:

awk '$1 >= "2025-03-01" && $1 <= "2025-03-15" { print }' log.txt

6. Geração de relatórios em múltiplos formatos

CSV

awk 'BEGIN { OFS=","; print "DATA,HORA,NIVEL,MENSAGEM" }
{ print $1, $2, $3, $4 }' log.txt > relatorio.csv

HTML

awk 'BEGIN {
    print "<html><body><table border='1'>"
    print "<tr><th>Data</th><th>Hora</th><th>Nível</th><th>Mensagem</th></tr>"
}
{
    printf "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", $1, $2, $3, $4
}
END { print "</table></body></html>" }' log.txt > relatorio.html

Markdown

awk 'BEGIN {
    print "| Data | Hora | Nível | Mensagem |"
    print "|------|------|-------|----------|"
}
{ printf "| %s | %s | %s | %s |\n", $1, $2, $3, $4 }' log.txt > relatorio.md

7. Scripts completos de relatórios automatizados

Um script Bash que encapsula a lógica awk com parâmetros flexíveis:

#!/bin/bash
# relatorio_log.sh - Gera relatório de logs

ARQUIVO="${1:-/var/log/syslog}"
NIVEL="${2:-ERRO}"
FORMATO="${3:-texto}"

if [ ! -f "$ARQUIVO" ]; then
    echo "Arquivo não encontrado: $ARQUIVO"
    exit 1
fi

case "$FORMATO" in
    csv)
        awk -v nivel="$NIVEL" 'BEGIN { OFS=","; print "LINHA,DATA,HORA,NIVEL,MENSAGEM" }
        $3 == nivel { print NR, $1, $2, $3, $4 }' "$ARQUIVO" > "relatorio_${NIVEL}.csv"
        echo "Relatório gerado: relatorio_${NIVEL}.csv"
        ;;
    html)
        awk -v nivel="$NIVEL" 'BEGIN {
            print "<html><body><h2>Relatório de " nivel "</h2><table border='1'>"
            print "<tr><th>Linha</th><th>Data</th><th>Hora</th><th>Mensagem</th></tr>"
        }
        $3 == nivel {
            printf "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", NR, $1, $2, $4
        }
        END { print "</table></body></html>" }' "$ARQUIVO" > "relatorio_${NIVEL}.html"
        echo "Relatório gerado: relatorio_${NIVEL}.html"
        ;;
    *)
        awk -v nivel="$NIVEL" 'BEGIN {
            printf "%-6s %-12s %-10s %-20s\n", "LINHA", "DATA", "HORA", "MENSAGEM"
            printf "%-6s %-12s %-10s %-20s\n", "------", "------------", "----------", "--------------------"
        }
        $3 == nivel {
            printf "%-6d %-12s %-10s %-20s\n", NR, $1, $2, $4
        }' "$ARQUIVO"
        ;;
esac

Uso: ./relatorio_log.sh /var/log/syslog ERRO html

8. Boas práticas e otimização de desempenho

  • Evite subprocessos: Não chame comandos externos dentro do awk se puder fazer com funções nativas
  • Use BEGIN/END: Inicialize variáveis e formate saída nos blocos especiais
  • Redirecione saídas: Use > no shell em vez de system("...") dentro do awk
  • Depuração: Use awk -v debug=1 e insira if (debug) print "variavel=" variavel no código
  • Arquivos temporários: Para processamentos grandes, use /tmp com nomes únicos via $$

Exemplo de depuração:

awk -v debug=1 '{
    if (debug) print "DEBUG: NR=" NR " NF=" NF > "/dev/stderr"
    total += $2
}
END { print total }' dados.txt

Referências