AWK é uma “arma secreta“: faz parte daquele conjunto de ferramentas que sempre estiveram ao seu lado (surge em 1977 e quase toda distribuição Linux a inclui), você a ignora durante toda a sua vida e em um belo dia resolve experimentá-la. É o momento em que aquele sentimento de “ENTÃO QUER DIZER QUE SOFRI TODOS ESTES ANOS A TOA???” aparece. :)
O que é AWK?
Trata-se de uma linguagem de programação cujo nome vêm das iniciais dos seus criadores (Alfred Aho, Peter Weinberger e Brian Kernighan – galerinha bruta) e nos permite escrever programas que tenham como fonte de dados arquivos texto. O mais impressionante é que você consegue resultados incríveis com pouquíssimas linhas de código.
É uma excelente ferramenta quando desejamos extrair inteligência de arquivos texto, especialmente logs. Sei que esta descrição é muito ruim, sendo assim, que tal irmos direto ao código?
Exemplo real: log de acesso do /dev/All
Sempre que alguém acessa o /dev/All é incluída uma linha em um arquivo de log nos informando a data, hora, IP do visitante e página visitada tal como no exemplo a seguir:
03/06/2015 10:20:00 186.213.73.169 http://www.devall.com.br 03/06/2015 10:20:01 34.13.3.69 http://www.devall.com.br 03/06/2015 10:20:20 186.213.73.169 http://www.devall.com.br 04/06/2015 10:20 186.213.73.169 http://www.devall.com.br/blog/info/3
Se quisermos saber quantos acessos tivemos por dia fica fácil pensar em um programa: dado que o caractere de espaço separa estes quatro campos basta escrever um parser simples na minha linguagem favorita e voilá: crio um acumulador para cada dia lendo o arquivo do início ao fim.
Mas e se eu te disser que consigo o mesmo efeito com apenas uma linha de AWK?
{contador[$1]++} END {for (j in contador) print j, contador[j]}
E como eu uso isto?
cat acessos.log | awk '{contador[$1]++} END {for (j in contador) print j, contador[j]}'
Gerando a saída:
03/06/2015 3 04/06/2015 1
Parece até mágica: agora vamos dissecar este código.
Entendendo o AWK
A sintaxe da linguagem é composta por dois elementos fundamentais: uma condição referente à linha corrente do arquivo sendo lido e o que deve ser executado caso esta condição seja satisfeita. Podemos resumir a sintaxe ao código abaixo:
condição {ações a serem executadas}
No nosso código temos claramente duas condições. Vamos à primeira:
{contador[$1]++}
Não há uma condição estabelecida, sendo assim o bloco entre chaves sempre será executado a cada linha lida. O código é tão simples que assusta: criamos uma tabela de hash cuja chave corresponda ao primeiro campo do arquivo.
Campo? Sim: por padrão AWK interpreta o caractere de espaço como um separador de campos. “$1” representa o primeiro campo (data), “$2” o segundo (hora) e por aí vai. Ao aplicar o operador “++” em meu hash, AWK automaticamente o interpreta como um valor numérico e o incrementa.
Ao final da leitura do arquivo temos uma tabela de hash tal como a exposta a seguir:
["03/06/2015" => 3, "04/06/2015" => 1]
Note que não precisamos declarar nossas variáveis antes de usá-las, o que nos gera um código muito mais simples. Agora vamos à segunda condição:
END { for (j in contador) print j, contador[j]}
AWK possuí algumas variáveis e condições embutidas. Dentre estas encontra-se “END”: uma condição que é satisfeita apenas quando chegamos ao final do arquivo. E o que fazemos quando isto ocorre? Geramos um relatório de saída ao nosso usuário final.
Apenas iteramos sobre as chaves presentes em nossa tabela de hash usando a estrutura de controle “for” e, para cada uma destas, iremos imprimir seu valor, seguido do número de acessos que fomos incrementando a cada linha do arquivo.
Outro exemplo: validando arquivos
Alguns anos atrás precisei interagir com um sistema que salvava notas fiscais em arquivos textuais similares ao exposto a seguir:
0100|Bolacha Mabel|3.40 0100|Leite Itambé|2.00 0200|5,40
O caractere “|” era usado para separar os campos e havia diferentes tipos de registros dentro do mesmo arquivo. Aqueles que começavam com “0100” representavam um item da nota fiscal (nome do item e valor pago respectivamente), e “0200” o valor total da nota. Uma validação simples deste arquivo é verificar se o valor total bate com o somatório do valor pago em cada item.
O validador em AWK abaixo ilustra melhor a linguagem. Iremos salvá-lo em um arquivo chamado “prog.awk”.
BEGIN {FS = "|"} $1 == "0100" { total += $3} $1 == "0200" && $2 != total { print "Valor inválido. Total = ", $2, "Somatório = ", total} $1 == "0200" && $2 == total {print "Arquivo OK"}
A primeira condição é executada quando o programa é iniciado, o que é representado pela condição “BEGIN”. Nesta linha definimos que o caractere separador de campos será “|” mudando o valor de uma variável embutida da linguagem que é o FS (Field Separator).
A segunda linha verifica se o campo 1 contém o texto “0100”. Se for o caso, iremos criar uma variável chamada “total” na qual iremos incrementar o valor presente no terceiro campo. (nota importante: evite o caractere vírgula ao lidar com números de pontos flutuante com AWK).
A terceira linha verifica se o arquivo é inválido. Primeiro checamos se o valor do primeiro campo é “0200” e se o valor do segundo campo desta linha é diferente ao que calculamos na variável “total”. Se for, iremos imprimir o erro para o usuário.
A quarta linha valida o arquivo fazendo o oposto do que foi feito na terceira.
E como executamos este programa no Linux?
cat notafiscal.txt | awk -f prog.awk
Mais recursos sobre AWK
O objetivo por trás deste post foi apenas mostrar com o que se parece a linguagem de programação para despertar sua curiosidade sobre o assunto. Claro que não cabiam aqui todos os detalhes a seu respeito, mas você os encontrará nos links abaixo:
AWK Community Portal – http://awk.info – o nome já diz tudo: é o portal da comunidade mundial com muitos links e textos interessantes a respeito.
AWK User Guide – http://www.math.utah.edu/docs/info/gawk_toc.html – é o texto que estou usando para me aprofundar na linguagem. Excelente leitura!
JAWK – http://jawk.sourceforge.net/ – Se você usa a JVM vai gostar de conhecer este projeto: é uma implementação completa do AWK para Java. Muito bom como linguagem embarcada!
AWK.net – https://awkdotnet.codeplex.com/ – Curte .net? Encontrei esta implementação, mas não cheguei a experimentá-la.
Notas finais
Minhas experiências recentes com AWK têm sido extremamente satisfatórias: em um de nossos projetos em que precisamos lidar com arquivos de log conseguimos um excelente ganho de produtividade conhecendo muito pouco a linguagem.
Espero que com este post mais pessoas incluam AWK em sua caixa de ferramentas, pois é algo que vale muito à pena aprender.
Deixe uma resposta