Introdução
Awk é uma linguagem procedural (ou imperativa) interpretada. As suas vantagens em relação
a C ou Pascal são:
- se for necessário fazer frequentemente alterações em vários arquivos texto, onde
quer que seja que certos padrões apareçam;
- se for necessário extrair dados de algumas linhas de um arquivo
enquanto o resto é descartado.
Ou seja, com awk é possível gerenciar tarefas simples de "reformatar dados" com apenas
algumas linhas de código.
Com awk é possível: gerenciar pequenos banco de dados pessoais; gerar
relatórios; validar dados; produzir índices, e fazer outras tarefas de
preparação de documentos; fazer experimentos com algoritmos que podem ser
adaptados posteriormente a outras linguagens de programação.
Função básica do awk é procurar por linhas (ou outras unidades de
texto) em arquivos que possuem certos padrões especificados no programa;
para cada "padrão" (pattern) deve haver uma ação associada, isto é, quando uma linha
corresponde a um dos padrões, awk realiza a ação especificada naquela linha;
depois, awk continua processando as linhas de entrada desta maneira até
encontrar o fim do arquivo de entrada; o conjunto de comandos "padrão-ação"
pode aparecer literalmente como um programa ou em um arquivo específico com a
opção "-f "; arquivos de entrada são lidos em ordem; se não há
arquivos, a entrada padrão é lida.
Programas em awk são diferentes dos programas na maioria das outras
linguagens porque são data-driven, isto é, deve-se descrever os dados com os
quais se quer trabalhar, e o que fazer quando encontrá-los. Em outras
linguagens procedurais normalmente é mais difícil descrever claramente os
dados que o programa irá processar. Por isso, programas awk são mais fáceis de
escrever e ler.
Quando awk é executado, deve-se especificar um programa awk que diga o que
ele tem que fazer. O programa consiste numa série de regras, sendo que cada
uma especifica um padrão que deve ser procurado e uma ação que deve ser tomada
quando o padrão é encontrado. Sintaticamente, uma regra consiste de um padrão
seguido de uma ação que deve estar entre { e }. Cada regra é separada por
uma nova linha. Então, um programa awk se parece com:
<padrão> { <ação> }
<padrão> { <ação> }
...
Executando um Programa awk
Existem várias maneiras de executar um
programa awk.
1) Se o programa é pequeno, é mais fácil
incluí-lo no comando de execução. Por exemplo:
awk '<Programa>' <Arq1Entrada> <Arq2Entrada> ...
<programa> consiste numa série de padrões e ações. Este comando é usado quando
programas simples são escritos no momento que se deseja utilizá-los. Este
formato de comando instrui o shell, ou interpretador de comandos, para
iniciar o awk e usar o programa para processar registros nos arquivos de
entrada. As aspas simples em torno do programa instruem o shell para: não
interpretar os caracteres awk como caracteres especiais do shell; e tratar
todo o programa como um único argumento para awk e permitir que o programa
tenha mais do que uma linha.
Também é possível executar o awk sem arquivos de entrada. Neste caso, quando
se digita a linha de comando:
awk '<programa> '
o awk aplica o programa à entrada padrão, o que siginifica que tudo que for
digitado no terminal até que seja pressionado CTRL+d. O exemplo a seguir copia
os caracteres digitados para a saída padrão.
$ awk '{ print }'
Este eh um exemplo
Este eh um exemplo
da utilizacao do awk!
da utilizacao do awk!
Fim!
Fim!
$ _
2) Quando o programa é extenso, é mais conveniente colocá-lo em um arquivo e executá-lo da
seguinte maneira:
awk -f <ArqPrograma> <Arq1Entrada> <Arq2Entrada> ...
-f <ArqPrograma> intrui o awk a pegar o programa do arquivo. Qualquer nome
pode ser usado no arquivo. Por exemplo, é possível colocar o programa:
BEGIN { print "Hello World!" }
no arquivo "exemplo". O comando para execução é:
awk -f exemplo
Se não fosse utilizado um arquivo, o comando para execução seria:
awk "BEGIN { print \"Hello World! \" }"
A contra-barra (\) é necessária antes de cada aspas (") interna devido às regras do
shell.
Considerando programas executáveis,
é possível escrever scripts em awk usando #!, que funciona em Linux e Unix derivados do
Berkeley Unix, System V Release 4 ou 3. Este mecanismo não funciona com sistemas mais antigos.
Por exemplo, o arquivo "exemplo" apresentado anteriormente pode ser alterado da
seguinte maneira:
#! /bin/awk -f
BEGIN { print "Hello World!" }
Depois, basta transformá-lo em um executável usando o comando chmod e
chamá-lo a partir do prompt.
$ chmod ugo+x exemplo
$ exemplo
Hello World
$ _
Isto é equivalente a usar o comando awk -f exemplo. A linha que começa com
#! contém o caminho e o nome do interpretador que deve ser executado, e
também comandos de inicialização opcionais que podem ser passados para o
interpretador. O sistema operacional então executa o interpretador com os
parâmetros fornecidos.
Exemplos
1) Exemplo simples
awk '/awk/ { print $0 }' texto.txt
Este comando executa um programa awk simples que procura pela string "awk" no
arquivo de entrada "texto.txt". Quando uma linha que contém "awk" é
encontrada, ela é exibida na tela do computador, pois print $0, ou
simplesmente print, significa imprimir a linha corrente. A / que aparece
antes e depois de awk indicam que awk é o padrão a ser procurado. As aspas
simples ao redor do programa notifica o shell para não interpretar os
caracteres como caracteres especiais do shell.
Este programa também poderia ser escrito em um arquivo, como por exemplo:
#! /bin/awk -f
BEGIN {
print "Ocorrencias: "
}
# padrao procurado
/\<awk\>/ { print }
Neste caso a execução seria:
$ <nome_arq_executavel> texto.txt
Numa regra awk tanto o padrão como a ação podem ser omitidos, mas não ambos.
Se o padrão é omitido, então a ação é executada para cada linha de entrada. Se
a ação é omitida, a ação default é imprimir todas as linhas que correspondam
ao padrão. Então, é possível eliminar a ação (o comando print) neste exemplo e
o resultado seria o mesmo. Por exemplo:
$ awk '/awk/' texto.txt
Porém,
omitir o comando print e manter {}, faz com que uma ação "vazia" seja
definida, isto é, nada é realizado. Por exemplo:
awk '/awk/ { }' texto.txt
2) Exemplo com duas regras
O interpretador awk lê os arquivos de entrada linha a linha, e para cada uma
ele testa o padrão especificado na(s) regra(s). Se vários padrões são
encontrados, então várias ações são executadas na ordem em que aparecem no
programa awk. Se nenhum padrão é encontrado, nenhuma ação é executada.
Por exemplo, o seguinte programa awk contém duas regras:
$ awk '/12/ { print $0 }
> /21/ { print $0 }' lista1.txt lista2.txt
A primeira regra possui a string 12 como padrão e print $0 como ação. A
segunda regra tem a string 21 como padrão e também print $0 como ação. As
ações sempre devem estar entre { e }. O programa exibe cada linha que contém a
string 12 ou 21. Se uma linha contém as duas strings, ela é exibida duas
vezes, uma para cada regra.
Exemplos de Programas de uma Linha
Muitos programas awk úteis possuem apenas uma ou duas linhas.
1) Programa que imprime o tamanho da maior linha.
$ awk '{ if (length($0) > max) max=length($0) }
END { print max }' texto.txt
2) Programa que imprime todas as linhas que possuem mais do que 80 caracteres.
A única regra possui uma expressão relacional e seu padrão, e não possui ação.
Portanto, a ação default, imprimir o registro, é tomada.
$ awk 'length($0) > 80' texto.txt
3) Este programa imprime cada linha que possui pelo menos um campo. Esta é uma
maneira simples de eliminar linhas em branco de um arquivo.
$ awk 'NF>0' texto.txt
4) Este programa imprime sete números randômicos de 0 a 100, inclusive.
$ awk 'BEGIN { for(i=1; 1<=7; i++)
print int(101*rand()) }'
5) Este programa imprime o número total de bytes usados pelos <arquivos>.
$ ls -lg <arquivos> | awk '{x+= $5}; END {print "total bytes:" x}'
6) Este programa conta o número de linhas em um arquivo
$ awk 'END { print NR }' <arquivo>
Expressões Regulares
Uma expressão regular é uma maneira de descrever um conjunto de strings. A expressão regular mais
simples é uma seqüência de letras, números, ou ambos.
Uma expressão regular pode ser usada como um padrão quando colocada entre /.
Neste caso ela é comparada com cada registro em todo o texto. Normalmente só é
necessário encontrar uma parte do texto para ter sucesso. O seguinte exemplo
imprime o segundo campo de cada registro que contém os três caracteres 'awk'
em qualquer posição.
$ awk '/awk/ { print $2 }' texto.txt
Expressões regulares também podem ser usadas na correspondência de expressões.
Estas expressões permitem especificar uma string a ser procurada que não
precisa ser todo o registro de entrada. Os operadores ~ e !~
realizam comparações de expressões regulares. Expressões que usam
estes operadores podem ser usadas como um padrão ou em comandos if, while, for
e do. Por exemplo:
~ /<expressão_regular>/
retorna verdadeiro se a
expressão <exp> corresponder a <expressão_regular>.
Os próximos exemplos são usados para "selecionar" todos os registros de entrada
que possuem a letra 'a' em algum lugar do primeiro campo.
$ awk '$1 ~ /a/' lista1.txt
$ awk '{ if ($1 ~ /a/) print }' lista1.txt
Quando uma expressão regular está entre /, ela é chamada de expressão regular
constante (5.27 é uma constante numérica e "awk" é uma constante do
tipo string).
Escape Sequences
Alguns caracteres não podem ser incluídos "literalmente" em expressões
regulares constantes. Neste caso eles são representados com escape sequences,
que são seqüências de caracteres que começam com \.
Isto é útil, por exemplo, para incluir " numa constante do tipo string. Por
exemplo:
$ awk 'BEGIN { print "Ele disse \"Oi!\" para ela." }'
O próprio caracter \ não pode ser incluído normalmente; deve-se colocar '\\'
para que um '\' faça parte da string.
O caracter \ também é usado para representar caracteres que não podem ser
exibidos, tais como tab ou newline. Outras utilizações são apresentadas abaixo:
\\ \
\/ /
\" "
\a Caracter de alerta (beep)
\t tab horizontal
\v tab vertical
\b Backspace
\f Formfeed
\n Newline
\r Carriage return
Comandos de Controle em Ações
Comandos de controle tais como if e while, controlam o fluxo de execução em
programas awk. A maioria deles são similares as comandos da linguagem C.
Comando if-else:
if (<condição>) { <comandos> }
if (<condição>) { <comandos> } else { <comandos> }
Exemplo:
if ( x % 2 == 0 )
print "x eh par"
else
print "x eh impar"
ou
if ( x % 2 == 0 ) print "x eh par"; else
print "x eh impar"
Comando while:
while (<condição>)
{
<comandos>
}
Exemplo:
$ awk '{ i = 1
while ( i <= 3 ) {
print $i
i++
}
}' lista1.txt
Comando do-while:
do {
<comandos>
} while ( <condição> )
Exemplo:
$ awk '{ i = 1
do {
print $0
i++
} while ( i<= 10 )
}' lista1.txt
Comando for:
for( <inicialização>; <condição>; <incremento> )
<comandos>
Exemplo:
$ awk '{ for (i=1; i<=3; i++)
print $i
}' lista1.txt
Comando break: Força o término imediato do laço de onde é chamado
(for, while ou do). Ele não tem significado quando usado fora do corpo
de um laço.
Comando continue: Assim como o break, é usado somente dentro de um laço
(for, while ou do). Ao invés de forçar o término do laço, este comando
faz com que ocorra a próxima iteração do laço, pulando qualquer código
intermediário.
Comando next: Força o awk a parar imediatamente de processar o registro
corrente e passar para o próximo registro.
Comando nextfile: Similar ao comando next, ao invés de abandonar o
processamento do registro corrente, o comando nextfile instrui o awk a
parar de processar o arquivo corrente.
Comando exit: Faz com que o awk pare imediatamente de processar a regra
corrente e pare de processar a entrada; qualquer entrada restante é
ignorada.
Comandos print e printf
O print é um comando de saída simples (padrão). Já o printf é usado quando
se deseja uma saída formatada.
O comando print exige simplesmente a especificação da lista de números,
strings, variáveis, expressões awk e/ou campos do registro corrente (tal
como $1), que devem ser exibidos, separados por vírgula. Eles são, então,
exibidos separados por espaços em branco e seguidos por uma nova linha.
Opcionalmente, a lista de números e strings pode ser colocada entre
parênteses.
O print sem um item especificado, é equivalente a print $0, e imprime todo o
registro corrente. Para imprimir uma linha em branco usa-se
print "", onde ""
é a string vazia.
Exemplos do comando print:
$ awk 'BEGIN { print "line one \nline two \nline three" }'
$ awk '{ print $1 $2 }' lista1.txt
$ awk 'BEGIN { print "Campo1 Campo2"
print "------ ------"
}
{ print $1, " ", $2 }' lista1.txt
O default no comando print é separar os campos com
um espaço em branco.
Entretanto, qualquer seqüência de caracteres pode ser usada, basta inicializar
a variável OFS (Output Field Separator). Da mesma maneira, é possível trocar a
forma de separar os registros, cujo default é uma nova linha. Neste caso,
deve-se inicializar a variável ORS (Output Record Separator). Por exemplo:
$ awk 'BEGIN { OFS = ";"; ORS = "\n\n" }
> { print $1, $2 }' lista1.txt
Carla;3226.12.12
Ivan;3340.11.00
Maria;3223.78.21
Regis;3332.12.21
Para se ter um controle mais preciso da formatação, bem como para alinhar
colunas de dados de uma maneira fácil, utiliza-se printf. Com printf é
possível especificar a largura que será usada para cada item e várias
maneiras de formatar números. Este comando é usado da seguinte maneira:
printf formato, item1, item2, ...
onde o formato é muito semelhante ao ANSI C (ex.: %c, %s, %i, %4.3f).
Exemplo da utilização do printf:
$ awk '{ printf "%-10s %s\n", $1, $2 }' lista1.txt
Carla 3226.12.12
Ivan 3340.11.00
Maria 3223.78.21
Regis 3332.12.21
Array em Awk
Um array é uma tabela de valores, chamados elementos. Os elementos de um array são
distinguidos por índices. Índices em awk podem ser tanto números como strings, e cada
array deve ter um nome (mesma sintaxe dos nomes de variáveis).
Arrays em awk são semelhantes às outras linguagens, mas existem algumas diferenças
fundamentais. Em awk não é necessário especificar o tamanho de um array antes de
começar a usá-lo. Adicionalmente, qualquer número ou string pode ser usado como
indice do array, e não apenas números inteiros consecutivos.
Na verdade, Arrays em awk são associativos. Isto significa que cada array é uma coleção
de pares: um índice, e o valor do elemento correspondente, como exemplificado abaixo:
Elemento 4 Valor 30
Elemento 2 Valor "awk"
Elemento 1 Valor 8
Elemento 3 Valor ""
Os pares acima estão fora de ordem porque a ordem é irrelevante.
Uma vantagem do array associativo é que novos pares podem ser adicionados
em qualquer momento. Para ilustrar, no exemplo anterior é possível inserir o valor
"numero 10" na décima posição:
Elemento 10 Valor "numero 10"
Elemento 4 Valor 30
Elemento 2 Valor "awk"
Elemento 1 Valor 8
Elemento 3 Valor ""
Desta maneira o array fica esparso, isto é, faltam alguns índices.
Outra conseqüência dos arrays associativos é que os índices não precisam ser
números inteiros positivos. Por exemplo, logo abaixo é apresentado um array que
traduz palavras do Inglês para o Francês:
Element "dog" Valor "chien"
Element "cat" Valor "chat"
Element "one" Valor "un"
Element 1 Valor "un"
Um cuidado que deve ser tomado, é que letras maiúsculas são diferentes de
minúsculas, e o valor de IGNORECASE não tem efeito. Deve-se usar a mesma
string para armazenar e recuperar um elemento do array.
Quando awk cria um array, por exemplo com uma função, o array criado
possuirá índices inteiros consecutivos que começam em um.
A principal maneira de usar um array é referenciar um dos seus elementos, da
seguinte maneira:
nome_array[índice]
Se for usada uma referência para um elemento que não está armazenado, o valor
resultante é "" (string nula).
É possível verificar se um elemento, num certo índice, existe em um array
com a expressão:
índice in nome_array
Assim, pode-se testar quando um índice existe ou não. Se nome_array[índice]
existe, é retornado o valor 1 (verdadeiro), caso contrário é retornado 0
(falso).
O exemplo abaixo ilustra a utilização de arrays.
#! /bin/awk -f
# Comandos que serao executados antes de varrer o arquivo
BEGIN {
print "Este programa ordena um arquivo cujas linhas sao numeradas!\n"
}
# Comandos que serao executados depois de varrer o arquivo
END {
print "Arquivo Ordenado: \n"
for (i=1; i<=max; i++)
if (i in vetor) # para evitar linhas em branco
print vetor[i]
}
# Comandos que serao executados enquanto varre o arquivo
{
if ($1 > max)
max = $1
vetor[$1] = $0
}
Para percorrer todos os elementos de um array cujos índices não são números
inteiros em ordem crescente, awk possui um comando for "especial":
for ( var in array )
<comandos>
Este laço executa os comandos uma vez para índice do array que tenha sido
previamente usado no programa, com a variável var "setada" para o índice.
Exemplo:
#! /bin/awk -f
# Exemplo com array cujos índices não são números inteiros
# Armazena 1 para cada palavra que é usada pelo menos uma vez
{
# NF número de campos do registro de entrada corrente
for (i=1; i<=NF; i++)
# armazena 1 para cada palavra que é usada pelo menos uma vez
pal_usadas[$i] = 1
}
# Comandos que serao executados depois de varrer o arquivo
END {
for (x in pal_usadas)
if (length(x)>10) {
++num_pal_longas
print x
}
print num_pal_longas, "palavras maiores que 10 caracteres!"
}
A ordem na qual os elementos do array são acessados por este comando é
determinada pelo awk e não pode ser controlada ou alterada.
É possível remover um elemento individual de um array usando o comando delete:
delete nome_array[índice]
No exemplo abaixo todos os elementos do array são removidos:
for (i in vetor)
delete vetor[i]
Torna-se importante salientar que remover um elemento é diferente de atribuir
um valor nulo. Por exemplo:
delete vetor[4]
if (4 in vetor)
print "Esta mensagem nunca sera exibida."
vetor[2] = ""
if (2 in vetor)
print "Esta mensagem sera exibida, apesar de nao ter conteudo."
Não é um erro remover um elemento que não existe, e todos os elementos podem
ser removidos com um único comando:
delete vetor
Awk suporta arrays multidimensionais. Neste caso são usados mais do que um
índice separados por vírgula. Também é possível testar quando um índice existe
em um array multidimensional usando o mesmo operador in. Exemplo:
$ awk '{
if (max_nf < NF)
max_nf = NF
# NR é o número de registros de entrada processados
# desde o início da execução do programa
max_nr = NR
for (x=1; x<=NF; x++)
vector[x, NR] = $x
}
END {
for (x=1; x<=max_nf; x++) {
for (y=max_nr; y>=1; --y)
printf ("%s", vector[x, y])
printf("\n")
}
}
}'
# Dada a entrada:
# 1 2 3 4 5 6
# 2 3 4 5 6 1
# 3 4 5 6 1 2
# 4 5 6 1 2 3
# O resultado é:
# 4 3 2 1
# 5 4 3 2
# 6 5 4 3
# 1 6 5 4
# 2 1 6 5
# 3 2 1 6