Precisa trabalhar com arquivos de texto ? AWK!

25 de jan de 2014 - Paulo Dias


Eae pessoal, nos últimos meses eu tive que usar bastante a linguagem C para manipular arquivos de texto (txt). Apesar de ser uma linguagem poderosa, trabalhar com esses arquivos em C pode ser bem complicado. Pensando nisso, quero mostrar nesse artigo uma linguagem mais especialiazada em manipulação de arquivos de texto ( lembre-se, para quem só sabe usar martelo, tudo é prego ).

Essa linguagem é a AWK, ela foi criada em 1977 pelos cientistas Alfred Aho, Peter J. Weinberger e Brian Kernighan no laboratório Bell Labs.

Para criar os exemplos vou utilizar o arquivo "/etc/passwd", e para que tudo ocorra de forma segura fiz uma copia desse arquivo para pasta home do meu usuário. Faça você também:


cp /etc/passwd $HOME 

O meu arquivo passwd têm essa aparência:

...
saned:x:110:120::/home/saned:/bin/false
ntp:x:111:121::/home/ntp:/bin/false
rtkit:x:114:123:RealtimeKit,,,:/proc:/bin/false
mdm:x:108:112:Gnome Display Manager:/var/lib/mdm:/bin/false
paulo:x:1000:1000:paulo:/home/paulo:/bin/bash

Perceba que é um arquivo CSV separado por ':' . Cada linha desse arquivo representa um usuário do sistema, sendo que os campos estão organizados assim:

login: senha : Id do usuario : Id do grupo : extras : pasta home : shell

Para iniciar, vou apenas usar o AWK para imprimir o arquivo no terminal, para isso digite o seguinte comando ( no terminal ) :


awk '{ print }' $HOME/passwd

O AWK fez a leitura de cada linha (uma por uma) do arquivo passwd e imprimiu na saida padrão( monitor ). Isso não é muito util, afinal, o comando cat também pode fazer isso e com uma sintaxe mais simples ( cat $HOME/passwd ). Agora vou tentar algo um pouco mais elaborado, vou imprimir o arquivo novamente, mas apenas o primeiro campo (login).


awk -F: '{ print $1 }' $HOME/passwd

Dessa vez eu utilizei o parametro -F, que especifica o separador de campos utilizado no arquivo ( nesse caso :, sem isso, o separador padrão é o TAB ou " " ) e passei a variável $1 ( que representa o primeiro campo ) para o print. Para cada campo da linha que esta sendo processada existe uma variável $n, onde n é a posicao do campo apartir do 1, A variavel $0 contém a linha inteira.

Para o próximo exemplo quero imprimir apenas quem utiliza o shell Bash:


awk -F: '{ if( $7 == "/bin/bash" ) print $1 }' $HOME/passwd

Obs. AWK utiliza os mesmos operadores condicionais e lógicos do C

Funcionou, mas ainda é possível melhorar a aparência dessa saída:


awk -F: ' { if( $7 == "/bin/bash" ) printf "%s usa o shell %s \n", $1, $7  }' $HOME/passwd

Obs. Perceba que o printf do AWK é quase igual ao do C, ele aceita vários formatos, por exemplo: %d, %i, %f, %s e outros.

Assim fica melhor, né? No próximo exemplo quero finalizar a saída com a quantidade de campos encontrados:


awk -F: '{ if( $7 == "/bin/bash" ) { printf "%s usa o shell %s \n", $1, $7; total++ } } END { printf "Total de registros %d \n", total }' $HOME/passwd

Nesse exemplo utilizei o bloco END, esse bloco é executado depois que o AWK processou todo conteúdo do arquivo. Diferente do C em AWK não é necessário instanciar uma variavel. Na primeira vez que uma variável é mencionada o AWK automaticamente cria uma instância e atribui um valor inicial, para números esse valor é 0 e para strings é "".

Caso seja necessário também é possível especificar um bloco BEGIN que é executado antes do AWK processe o conteúdo do arquivo.

O exemplo anterior ficou um pouco grande para ser executado direto no terminal, para facilitar a escrita, é possivel utilizar a AWK em forma de script, para isso basta criar um arquivo que inicie com a linha "#!/usr/bin/awk -f" e escrever os comandos nas linhas seguintes. Vai ser necessário fazer algumas adaptações, exemplo anterior em forma de script fica assim:

#!/usr/bin/awk  -f

BEGIN {
        FS=":";
}

{
     if( $7 == "/bin/bash") {

        printf "%s usa o shell %s \n", $1, $7;
        total++;
     }
}

END {
     printf "Total de registros %d \n", total
}

A principal diferença é que no lugar de especificar o separador através do parametro -F, utilizo a variavel padrão FS, dentro do bloco BEGIN.

Salvei o script no arquivo teste.awk, depois dei permissão de execução para ele através do comando "chmod +x teste.awk" e para executar basta escrever no terminal "./teste.awk $HOME/passwd" . Assim fica parecido com shell script, né? ( não sabe o que é shell script? clique aqui )

Continuando, agora eu quero mostrar apenas as linhas onde o login tenha menos de 4 letras:

#!/usr/bin/awk  -f

BEGIN {
        FS=":";
}

{
     if( length( $1 ) < 4) {

        printf "%s \n", $1;
        total++;
     }
}

END {
     printf "Total de registros %d \n", total
}

Nesse exemplo utilizei a função length que retorna o tamanho de uma string. Algumas das funções do AWK para trabalhar com strings são:

index( s1, s2 ) retorna a posição da string s1 na string s2
gsub( sO, sS, s ) substitui a string original sO pela a string substituta sS na sting s
length( s ) retorna o tamanho da string s
split( s, v, d ) quebra a string s usando o delimitador d e coloca no vetor v
substr( s, i, f ) retorna a parte da string s que inicia na posição i e termina na posição f

AWK também têm funções para trabalhar com números, alguma delas são:

sqrt( n ) raiz quadrada
sen( n ) seno
cos( n ) cosseno
int( n ) a parte inteira de n

No próximo exemplo vou redirecionar a saída para um arquivo e vou alterar o shell de todos os usuários que usam o "/bin/sh" para o "/bin/bash":

#!/usr/bin/awk  -f

BEGIN {
        FS=":";
}

{
     if( $7 == "/bin/sh") $7 = "/bin/bash";

     for( i = 1; i < 8; i++ ) printf "%s:", $i  >> "bkp";

     print "" >> "bkp"; #pra quebrar a linha
}

Obs1. A AWK também possui uma instrução while, que assim como o for tem uma sintaxe idêntica a do C
Obs2. Os comentários em AWK são feitos com #

O exemplo anterior foi escrito ao estilo C propositalmente, eu queria mostrar como é a instrução for, o jeito AWK de fazer isso é utilizando a função gsub, assim:


#!/usr/bin/awk  -f
{
    gsub( "/bin/sh", "/bin/bash", $0 )

    print $0 > "bkp"
}

Pessoal por hoje é isso, tem muito mais para mostrar sobre o AWK, mas fica para um próximo artigo. Para quem quiser continuar estudando o AWK, aqui fica alguns links...

http://www.inf.pucrs.br/~manssour/AWK/
http://pt.wikipedia.org/wiki/AWK
http://br-linux.org/artigos/awk_intro.htm

Paulo Dias

Graduado no curso tecnólogo em análise e desenvolvimento de sistemas. Defensor do Software Livre e da democratização da informação. Possui as certificações Linux LPIC-1 e Java OCA. Atualmente exerce a função de coordenador técnico na área de telecomunicações.

Siga-me no Twitter