awk avançado
Esta aula aprofunda o uso do awk, cobrindo variáveis, operações, estruturas de controle, blocos BEGIN/END e a geração de relatórios formatados. Você aprenderá a transformar o awk em uma ferramenta poderosa para processamento de texto e análise de dados em shell.
O awk é uma das ferramentas mais poderosas do ambiente Unix/Linux para processamento de texto e geração de relatórios. Nesta aula avançada, vamos explorar recursos que vão além do básico: variáveis, operadores, estruturas de controle, blocos especiais BEGIN e END, e a criação de relatórios estruturados. Se você já domina a sintaxe básica do awk (padrões e ações), está pronto para elevar seu nível e resolver problemas complexos de manipulação de dados com elegância e eficiência.
O awk funciona como uma mini linguagem de programação, com suporte a variáveis, arrays, loops e funções. Ele lê um arquivo linha por linha, divide cada linha em campos (por padrão, separados por espaços) e executa ações baseadas em padrões. A beleza do awk está na sua concisão: muitas vezes, uma única linha de awk substitui dezenas de linhas de código em outras linguagens. Vamos mergulhar nos recursos avançados que o tornam indispensável no dia a dia de quem trabalha com shell.
Variáveis e operações
O awk permite criar e manipular variáveis de forma dinâmica, sem declaração de tipo. As variáveis podem ser usadas para armazenar números, strings, resultados de operações, e até mesmo arrays associativos. A atribuição é feita com o sinal =, e o valor pode ser usado em expressões aritméticas, comparações e concatenação de strings.
Os operadores aritméticos incluem +, -, *, /, % (módulo) e ^ (potência). Para strings, a concatenação é feita simplesmente justapondo os valores ou usando o operador . (ponto) em versões mais novas do awk (gawk). Também existem operadores de atribuição combinada, como +=, -=, etc.
Exemplo prático: suponha que temos um arquivo notas.txt com o nome de alunos e três notas, e queremos calcular a média de cada um e a média geral.
$ cat notas.txt
João 8 9 7
Maria 10 8 9
Pedro 6 7 8
$ awk '{soma = $2 + $3 + $4; media = soma/3; printf "%s média: %.2f\n", $1, media; soma_geral += media; cont++} END {printf "Média geral: %.2f\n", soma_geral/cont}' notas.txtNo exemplo, soma e media são variáveis locais criadas na ação. O operador += acumula a soma das médias em soma_geral, e cont conta o número de registros. No bloco END, calculamos a média geral.
Além de variáveis simples, o awk suporta arrays associativos, que são extremamente úteis para agrupar dados. Por exemplo, para contar quantas vezes cada nome aparece em um arquivo, podemos usar um array indexado pelo nome.
$ cat nomes.txt
Ana
Bruno
Ana
Carlos
Ana
$ awk '{contagem[$1]++} END {for (nome in contagem) print nome, contagem[nome]}' nomes.txtEsse exemplo mostra o poder dos arrays associativos: a linha contagem[$1]++ incrementa o valor correspondente à chave $1 (o nome). No final, percorremos o array com um loop for e exibimos cada nome e sua contagem.
Condicionais e loops
O awk suporta as estruturas de controle clássicas: if/else, while, do...while e for. Elas permitem criar lógica complexa dentro de um script awk, seja na linha de comando ou em um arquivo de script.
O if pode ser usado tanto como padrão (antes das chaves) quanto como comando dentro de uma ação. Por exemplo, para listar apenas os usuários com UID maior que 1000 no /etc/passwd:
$ awk -F: '$3 > 1000 {print $1, $3}' /etc/passwdMas também podemos usar if dentro da ação para decisões mais complexas:
$ awk -F: '{if ($3 > 1000) print $1, "é um usuário comum"; else print $1, "é um usuário de sistema"}' /etc/passwdOs loops são úteis para percorrer arrays ou processar campos de forma iterativa. Por exemplo, para somar todos os campos de uma linha (assumindo que são números):
$ echo "10 20 30 40" | awk '{soma=0; for(i=1; i<=NF; i++) soma += $i; print "Soma:", soma}'Aqui, NF é uma variável automática que contém o número de campos na linha atual. O loop for percorre de 1 até NF, acumulando a soma.
O while pode ser usado quando não sabemos quantas iterações serão necessárias. Por exemplo, para imprimir os campos de uma linha em ordem reversa:
$ echo "a b c" | awk '{i=NF; while(i>0) {printf "%s ", $i; i--} print ""}'Essas estruturas tornam o awk uma linguagem completa de programação, capaz de resolver problemas que exigiriam scripts maiores em outras linguagens.
BEGIN e END
Os blocos BEGIN e END são especiais no awk. O bloco BEGIN é executado antes de ler a primeira linha de entrada, e o bloco END é executado após processar todas as linhas. Eles são ideais para inicializar variáveis, imprimir cabeçalhos, resumos e relatórios finais.
Por exemplo, para gerar um relatório com cabeçalho e rodapé, podemos usar:
$ awk 'BEGIN {print "Relatório de Notas"; print "----------------"} {print $1, "-", $2} END {print "----------------"; print "Fim do relatório"}' notas2.txtO bloco BEGIN também é usado frequentemente para definir o separador de campos (FS) e o separador de saída (OFS) antes do processamento. Por exemplo, para processar um arquivo CSV:
$ awk 'BEGIN {FS=","; OFS="|"} {print $1, $2}' dados.csvO bloco END é perfeito para calcular totais, médias, contagens e outras estatísticas após processar todos os dados. Por exemplo, para somar os valores da segunda coluna de um arquivo:
$ awk '{soma += $2} END {print "Total:", soma}' valores.txtEsses blocos dão ao awk uma estrutura de programa: inicialização, processamento e finalização. Isso é essencial para gerar relatórios bem formatados, como veremos a seguir.
Relatórios
Uma das aplicações mais comuns do awk é a geração de relatórios formatados. Com as funções de formatação printf e sprintf, e o uso de variáveis e loops, podemos criar tabelas alinhadas, resumos estatísticos e até relatórios com quebras de página.
Vamos construir um relatório de vendas a partir de um arquivo com formato produto;quantidade;preço_unitário. Queremos um relatório com cabeçalho, linhas detalhadas, e um total no final.
$ cat vendas.csv
Produto;Quantidade;Preço
Caneta;10;2.50
Caderno;5;15.00
Lápis;20;1.00
$ awk -F';' 'NR==1 {print "Produto\tQuantidade\tPreço\tTotal"; print "-----------------------------------"; next} {total = $2 * $3; printf "%-10s\t%d\t\t%.2f\t%.2f\n", $1, $2, $3, total; soma_total += total} END {print "-----------------------------------"; printf "Total geral: %.2f\n", soma_total}' vendas.csvExplicação: NR==1 é um padrão que corresponde à primeira linha (cabeçalho). Nesse caso, imprimimos o cabeçalho do relatório e usamos next para pular para a próxima linha. Nas demais linhas, calculamos o total e usamos printf com formatação: %-10s alinha o nome à esquerda em 10 caracteres, %d para inteiros, %.2f para números com duas casas decimais. No final, o bloco END imprime o total geral.
Outra técnica útil é gerar relatórios com totais por categoria. Suponha que temos um arquivo com vendas por região e queremos um resumo por região. Podemos usar um array associativo para acumular os totais por região.
$ cat vendas_por_regiao.txt
Norte 100
Sul 150
Norte 200
Leste 120
Sul 180
$ awk '{total[$1] += $2} END {print "Região\tTotal"; for (regiao in total) printf "%s\t%.2f\n", regiao, total[regiao]}' vendas_por_regiao.txtEsse relatório agrupa as vendas por região e exibe o total de cada uma. A ordem dos itens no array não é garantida, mas podemos ordenar usando asort ou asorti (no gawk) se necessário.
Para relatórios mais complexos, podemos combinar loops, condicionais e formatação. Por exemplo, podemos destacar vendas acima de um certo valor, ou adicionar linhas de separação. O awk permite isso com facilidade.
Boas práticas e observações finais
Ao usar awk avançado, é importante lembrar que a clareza e a manutenibilidade do código são fundamentais. Para scripts longos, é recomendável escrever o awk em um arquivo separado e executá-lo com awk -f script.awk dados.txt. Isso facilita a leitura e a reutilização.
Outra boa prática é sempre usar aspas ao redor das expressões awk na linha de comando, para evitar que o shell interprete caracteres especiais. Além disso, é útil comentar o código quando a lógica for complexa.
O awk é extremamente poderoso, mas não é a ferramenta certa para todos os problemas. Para processamento de texto simples, ferramentas como grep e sed podem ser mais adequadas. Para transformações de dados mais complexas, talvez valha a pena considerar outras linguagens como Python ou Perl. No entanto, quando você precisa de uma solução rápida e eficiente dentro do shell, o awk é imbatível.
Referências
- GNU Awk User's Guide
- GNU Awk String Functions
- GNU Awk Numeric Functions
- GNU Awk Control Statements
- GNU Awk Arrays
- POSIX awk Specification
- man awk
Exercícios
- Escreva um comando awk que leia um arquivo com números (um por linha) e imprima a soma e a média desses números. Use variáveis e o bloco END.✓ Resposta:
awk '{soma += $1; cont++} END {if (cont > 0) printf "Soma: %.2f\nMédia: %.2f\n", soma, soma/cont; else print "Nenhum número"}' numeros.txt - Dado um arquivo CSV com nome, idade e cidade (separados por vírgula), use awk para imprimir apenas as pessoas com mais de 30 anos, no formato "Nome (Cidade) - Idade".✓ Resposta:
awk -F',' '$2 > 30 {printf "%s (%s) - %s\n", $1, $3, $2}' dados.csv - Usando um array associativo, conte quantas vezes cada palavra aparece em um arquivo de texto e imprima em ordem alfabética. Dica: use
for (palavra in contagem)e depois ordene comsortse necessário.✓ Resposta:awk '{for (i=1; i<=NF; i++) contagem[$i]++} END {for (palavra in contagem) print palavra, contagem[palavra]}' texto.txt | sort - Crie um relatório a partir de um arquivo com departamento e salário (ex.: "Vendas 5000"). O relatório deve mostrar o total de salários por departamento, ordenado pelo total (use pipe com sort).✓ Resposta:
awk '{total[$1] += $2} END {for (dept in total) print dept, total[dept]}' salarios.txt | sort -k2 -nr - Escreva um script awk que processe um arquivo de log com linhas no formato "data hora mensagem" e imprima apenas as mensagens que contêm a palavra "ERRO", contando quantas vezes cada tipo de erro aparece (considere que a mensagem é o terceiro campo em diante).✓ Resposta:
awk '$3 ~ /ERRO/ {msg=""; for (i=3; i<=NF; i++) msg = msg $i " "; contagem[msg]++} END {for (m in contagem) print contagem[m], m}' log.txt