Você está na página 1de 9

Awk em Exemplos, Parte 3

funes string e ...controles de cheques?


Daniel Robbins
Presidente/CEO, Gentoo Technologies, Inc.
Abril de 2001
Em sua concluso desta srie sobre o awk, Daniel introduz algumas funes string importantes do awk, e
ento mostra como escrever um programa de balano de cheques completo do princpio. Junto, voc ir
aprender como escrever suas prprias funes, e usar os arrays multidimensionais do awk. No fim do
artigo, voc vai ter mais experincia com o awk, permitindo qeu voc crie scripts mais poderosos.

Formatando a sada
Funes string
Substituies de string
Formas de string especiais
Diverso financeira
O cdigo
Funes financeiras
O bloco principal
Gerando o relatrio
Atualizaes
Recursos
Sobre o autor

Formatando a sada
Mesmo que a declarao print do awk faa o trabalho a maioria das vezes, s vezes necessrio mais.
Para estas ocasies, o awk oferece duas velhas amigas chamadas printf() e sprintf(). Sim, estas funo,
como muitas outras partes do awk, so idnticas s suas contrapartes do C. O printf() ir escrever uma
string formatada em stdout, enquanto sprintf() retorna uma string formatada que pode ser atribuda a uma
varivel. Se voc no est familiarizado com printf() e sprintf(), um artigo introdutrio de C ir introduzir
rapidamente estas duas funes de impresso. Voc pode ver a pgina man do printf() escrevendo "man 3
printf" em seu sistema Linux.
Aqui temos um cdigo awk exemplo com o sprintf() e printf(). Como voc pode ver, tudo quase
idntico ao C.
x=1
b="foo"
printf("%s got a %d on the last test\n","Jim",83)
myout=sprintf("%s-%d",b,x)
print myout
O cdigo ir escrever:
Jim got a 83 on the last test
foo-1

Funes string
O awk possui funes string em abundncia, e isto bom. No awk, as funes string so realmente
necessrias, pois no possvel tratar uma string como um array de caracteres como em outras
linguagens, como o C, C++ e Python. Por exemplo, se o cdigo abaixo for executado:
mystring="How are you doing today?"
print mystring[3]
ser gerada uma mensagem de erro como a abaixo:
awk: string.gawk:59: fatal: attempt to use scalar as array
Apesar de no serem to convenientes quanto os tipos seqncia do Python, as funes string do awk
servem fazem o servio. Vamos dar uma olhada nelas.
Primeiro, temos a funo bsica length(), que retorna o comprimento de uma string. Veja como us-la:
print length(mystring)
Este cdigo ir imprimir o valor:
24
OK, vamos adiante. A prxima funo de string chamada index, e ir retornar a posio da ocorrncia
de uma substring em outra string. Ela ir retornar 0 se a string no for encontrada. Usando mystring,
podemos usar a funo desta forma:
print index(mystring,"you")
O awk escreve:
9
Vamos passar agora para duas funes mais fceis, tolower() e toupper(). Como voc pode estar
adivinhando, estas funes iro retornar a string com todos os caracteres convertidos para minsculas ou
maisculas, respectivamente. Note que tolower() e toupper() retornam a nova string, e no modificam a
original. Este cdigo:
print tolower(mystring)
print toupper(mystring)
print mystring
Ir produzir esta sada:
how are you doing today?
HOW ARE YOU DOING TODAY?
How are you doing today?
At agora, tudo bem, mas exatamente como selecionamos uma substring ou mesmo um nico caracter de
uma string? aqui que substr() vem. A chamada a substr() feita assim:
mysub=substr(mystr,startpos,maxlen)

mystring deve ter ou uma varivel string ou uma string literal da qual voc quer extrair uma substring.
startpos deve estar configurada para o caracter de incio, e maxlen deve conter o comprimento mximo da
string que deve ser extrada. Note que eu disse comprimento mximo. Se lengh(mystring) mais curto
que startpos+maxlen, o resultado ser truncado. substr() no ir modificar a string original, mas ir
retornar a substring. Veja um exemplo:
print substr(mystring,9,3)
O awk ir escrever
you
Se voc programa regularmente em uma linguagem que usa ndices de array para acessar partes de uma
string (e quem no faz?), faa uma nota mental que o substr() o substituto awk. Voc precisar us-lo
para extrair caracteres e substring, e como o awk uma linguagem baseada em strings, voc ir utilizar
isto com freqncia.
Agora vamos passar para algumas funes com mais substncia, a primeira chamada match(). O
match() bastante parecida com o index(), exceto que em vez de procurar por uma substring como o
index() faz, ele procura por uma expresso regular. A funo match() ir retornar a posio inicial da
combinao, ou zero se no houver combinao. Alm disso, o match() ir configurar duas variveis
chamadas RSTART e RLENGTH. RSTART contm o valor de retorno (a localizao da primeira
combinao), e RLENGTH ir conter seu comprimento em caracteres (ou -1 se nenhuma combinao for
encontrada). Usando RSTART, RLENGTH, substr(), e um pequeno lao, voc pode facilmente fazer
iteraes sobre todas as combinaes encontradas em sua string. Veja um exemplo da chamada ao
match():
print match(mystring,/you/), RSTART, RLENGTH
O awk ir escrever:
9 9 3

Substituies de string
Vamos, agora, olhar duas funes de substituio de string, sub() e gsub(). Estas so diferentes das
funes anteriores por que modificam a string original. Veja um modelo que mostra como chamar sub():
sub(regexp,replstring,mystring)
Quando voc chama sub(), ela ir procurar a primeira seqncia de caracteres em mystring que combina
com a regexp, e ir substituir aquela seqncia com replstring. sub() e gsub() possuem argumentos
idnticos, a nica coisa que diferencia elas que sub() ir substituir a primeira combinao com regexp
que encontrar (se houver alguma), e o gsub() executa uma substituio global, trocando todas as
combinaes de regexp. Veja um exemplo da chamada de su() e gsub():
sub(/o/,"O",mystring)
print mystring
mystring="How are you doing today?"
gsub(/o/,"O",mystring)
print mystring
Precisamos reconfigurar mystring para seu valor original por que a primeira chamada a sub() modificou
diretamente mystring. Quando executado, este cdigo far com que o awk apresente:

HOw are you doing today?


HOw are yOu dOing tOday?
Obviamente expresses regex mais complexas so possveis. Fica a critrio do leitor testar algumas
regexp mais complicadas.
Completaremos nossa cobertura das funes de string introduzindo a funo split(). O trabalho de split()
"cortar" uma string, e colocar as vrias partes em um array indexada por inteiro. Aqui temos um exemplo
da chamada a split():
numelements=split("Jan,Feb,Mar,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
",mymonths,",")
Quando chamamos split(), o primeiro argumento contm a string literarl ou varivel string a ser cortada.
No segundo argumento, voc especifica o nome do array que split() ir preencher com as partes que ele
cortar. No terceiro elemento, especifique o separador que ser usado para cortar as strings. Quando split()
retorna, ela retorna o nmero de elementos string que foram divididos. O split() atribui cada um a um
ndice de array iniciando em um, de forma que o seguinte cdigo:
print mymonth[1],mymonth[numelements]
...ir escrever
Jan Dec

Formas de string especiais


Uma nota rpida -- quando chamar length(), sub(), ou gsub(), voc pode omitir o ltimo argumento, e o
awk ir aplicar a funo a $0 (a linha atual inteira). Para escrever o comprimento de cada linha em um
arquivo, use este script awk:
{
print length()
}

Diverso financeira
Algumas semanas atrs, eu decidi escrever meu prprio programa de balano de cheques em awk. Eu
decidi que usaria um arquivo texto simples delimitado por tabulaes no qual eu informaria meus
depsitos e retiradas mais recentes. A idia era passar estes dados a um script awk que iria acrescentar
automaticamente todas as somas e informar o balano. Aqui est como eu decidi registrar todas minhas
transaes no meu "ASCII checkbook":
23 Aug 2000

food

Jimmy's Buffet 30.25

Cada campo no arquivo separado por uma ou mais tabulaes. Depois da data (campo 1, $1), existem
dois campos chamados "categoria de despesa" (expense category) e "categoria de entrada" (income
category). Quando estou informando uma despesa conforme a linha acima, eu coloco um apelido de
quatro letras no campo exp, e um "-" (entrada em branco) no campo inc. Isto significa que este item
particular um "gasto com alimentao" :) Um depsito ir se parecer com isto:

23 Aug 2000

inco

Boss Man

2001.00

Neste caso, eu coloquei um "-" (branco) na categoria exp, e coloquei "inco" na categoria inc. "inco" meu
apelido para entradas genricas (tipo contracheque). O uso de apelidos para as categorias me permite
gerar um detalhamento de gastos e pagamentos por categoria. Sobre o resto dos registros, todos os outros
campos so bem auto-explicativos. O campo cleared? ("Y" ou "N") registra se a transao j foi feita na
minha conta, depois disso temos uma descrio da transao, e uma quantia positiva de dlares.
O algoritmo usado para calcular o balano no muito difcil. O awk simplesmente precisa ler cada linha,
uma por uma. Se uma categoria de despesa listada mas no h uma categoria de entrada ( "-"), ento
temos um dbito. Se uma categoria de entrada listada, mas nenhuma categoria de despesa ("-"), ento a
quantia de dlares um crdito. E, se tanto a categoria de entrada e despesa so listadas, ento a quantia
uma "transferncia de categoria", ou seja, a quantia de dlares ser subtrada da categoria de despesa e
acrescentada categoria de entrada. Novamente, todas estas categorias so virtuais, mas so bastante teis
para controlar entradas e despesas, bem como o oramento.

O cdigo
hora de dar uma olhada no cdigo. Comeamos com a primeira linha, o bloco BEGIN e uma definio
de funo:

balance, parte 1
#!/usr/bin/env awk -f
BEGIN {
FS="\t+"
months="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"
}
function monthdigit(mymonth) {
return (index(months,mymonth)+3)/4
}
Acrescentando a primeira linha "#!..." a qualquer script awk ir permitir que o mesmo seja executado
diretamente do shell, desde que se faa um "chmod +x myscript" primeiro. As linhas restantes definem
nosso bloco BEGIN, que executadoantes que o awk comece a processar nosso arquivo de cheques.
Configuramos FS (o separador de campos) para ser "\t+", o que diz ao awk que os campos sero
separados por uma ou mais tabulaes. Alm disto, definimos uma string chamada months que usada
por nossa funo monthdigit(), que aparece em seguida.
As ltimas trs linhas mostram como definir sua prpria funo awk. O formato simples -- escreva
"function", e o nome da funo, e ento os parmetros, separados por vrgulas, entre parntesis. Depois
disto, um bloco de cdigo "{}" contm o cdigo que voc quer que a funo execute. Todas funes
podem acessar variveis globais (como nossa varivel months). Alm disto, o awk tem a declarao
"return", que permite que a funo retorne um valor, e opera de forma similar ao "return" do C, Python, e
outras linguagens. Esta funo particular converte um nome de ms em string de trs caracteres para o seu
equivalente numrico. Por exemplo, este cdigo:
print monthdigit("Mar")
...ir escrever isto:
3
Agora hora de avanar para outras funes.

Funes financeiras
Existem trs funes que executam o controle financeiro para ns. Nosso bloco de cdigo principal, que
iremos ver logo, processa cada linha do talonrio em seqncia, chamando cada uma destas funes de
forma que a transao apropriada registrada em um array awk. Existem trs tipos bsicos de transao,
crdito (doincome), dbito (doexpense), e transferncia (dotransfer). Voc notar que todas as trs
funes aceitam um argumento, chamado mybalance. mybalance um "placeholder" para um array de
duas dimenses, que iremos passar s funes como um argumetno. At agora, no havamos tratado de
arrays multi-dimensionais, entretanto, conforme pode ser visto abaixo, a sintaxe bastante simples. Basta
separar cada dimenso com uma vrgula, e est tudo certo.
Iremos armazenar as informaes em "mybalance" da seguinte forma: a primeira dimenso do array vai
de 0 a 12, e especifica o ms, ou zero para o ano inteiro. Nossa segunda dimenso uma categoria de trs
letras, como "foo" ou "inco", e a categoria que estamos tratando. Assim, para encontrar o balano total
para a categoria food, basta olhar em mybalance[0,"food"]. Para encontrar a entrada de junho, basta olhar
em mybalance[6,"inco"].

balance, parte 2
function doincome(mybalance) {
mybalance[curmonth, $3] += amount
mybalance[0,$3] += amount
}
function doexpense(mybalance) {
mybalance[curmonth,$2] -= amount
mybalance[0,$2] -= amount
}
function dotransfer(mybalance) {
mybalance[0,$2] -= amount
mybalance[curmonth,$2] -= amount
mybalance[0,$3] += amount
mybalance[curmonth,$3] += amount
}
Quando doincome() ou qualquer uma das outras funes chamada, a transao registrada em dois
lugares -- mybalance[0,category] e mybalance[curmonth,category], o balano da categoria para o ano
inteiro e o balano da categoria para o ms atual, respectivamente. Isto nos permite gerar facilmente um
relatrio anual ou detalhado por ms de entrada/despesas.
Se voc olhar estas funes, ir notar que o array referenciado por mybalance passado por referncia.
Alm disto, tambm nos referimos a vrias variveis globais: curmonth, que tem o valor numrico do ms
do registro atual, $2 (a categoria de despesa), $3 (a categoria de entradas), e amount ($7, a quantia de
dlares). Quando doincome() e as outras funes so chamadas, todas estas variveis devem ter sido
configuradas corretamente para o registro atual (linha), o registro que est sendo processado.

O bloco principal
Aqui est o bloco principal de cdigo que contm o cdigo que trata cada linha de dados de entrada.
Lembre-se, como estamos configurando o FS corretamente, podemos nos referir ao primeiro campo como
$1, o segundo campo como $2, etc. Quando doincome() e outros so chamados, as funes podem acessar

os valores atuais de curmonth, $2, $3 e amount que esto definidos fora das funes. D uma olhada no
cdigo em acompanhe a explicao que vem depois.

balance, parte 3
{
curmonth=monthdigit(substr($1,4,3))
amount=$7
#record all the categories encountered
if ( $2 != "-" )
globcat[$2]="yes"
if ( $3 != "-" )
globcat[$3]="yes"
#tally up the transaction properly
if ( $2 == "-" ) {
if ( $3 == "-" ) {
print "Error: inc and exp fields are both
blank!"
exit 1
} else {
#this is income
doincome(balance)
if ( $5 == "Y" )
doincome(balance2)
}
} else if ( $3 == "-" {
#this is an expense
doexpense(balance)
if ( $5 == "Y" )
doexpense(balance2)
} else {
#this is a transfer
dotransfer(balance)
if ( $5 == "Y" )
dotransfer(balance2)
}
}
No bloco principal, as duas primeiras linhas configuram curmonth para um valor inteiro entre 1 e 12, e
configuram o valor de amount para o valor do campo 7 (para tornar o cdigo fcil de entender). A seguir,
temos cinco linhas interessantes, em que escrevemos os valores em um array chamado globcat. globcat,
ou array de categorias globais, usado para gravar todas as categorias encontradas no arquivo -- "inco",
"misc", "food", "util", etc. Por exemplo, se $2 == "inco", fazemos globcat["inco"] ser "yes". Mais tarde,
podemos iteragir pela lista de categorias com um simples lao "for (x in globcat)".
Nas prximas vinte linhas, analisamos o campo $2 e $3, e registramos a transao apropriadamente. Se
$2=="-" e $3!="-", temos uma entrada, e ento chamamos doincome(). Se a situao inversa, chamamos
doexpense(). Se tanto $2 quano $3 contm categorias, chamamos dotransfer(). Cada vez, passamos o
array "balance" para estas funes de forma que os dados apropriados so gravados no mesmo.
Voc tambm deve ter percebido vrias linhas com "if ( $5 == "Y" ), registre a mesma transao em
balance2". O que exatamente estamos fazendo a? Lembre-se que $5 contm um "Y" ou um "N", e
registra se a transao foi efetivada em nossa conta. Como ns registramos a transao em balance2
somente se a transao foi efetivada, balance2 contm o balano real da conta, enquanto "balance" ir
conter todas as transaes, efetivadas ou no. Voc pode usar balance2 para verificar sua entrada de
dados (j que deve ser idntica ao saldo de sua conta bancria de acordo com seu banco), e usar "balance"

para certificar-se que voc no ir ultrapassar o saldo de sua conta (uma vez que ir levar em conta
quaisquer cheques que tenham sido passados e que no tenham sido descontados).

Gerando o relatrio
Depois que o bloco principal tenha processado cada registro de entrada, temos um registro compreensivo
dos dbitos e crditos divididos por categorias e por ms. Agora, tudo que precisamos definir um bloco
END que gere um relatrio, um bloco modesto neste caso:

balance, parte 4
END {
bal=0
bal2=0
for (x in globcat) {
bal=bal+balance[0,x]
bal2=bal2+balance2[0,x]
}
printf("Your available funds: %10.2f\n", bal)
printf("Your account balance: %10.2f\n", bal2)
}
Este trecho escreve um resumo parecido com o seguinte:
Your available funds:
Your account balance:

1174.22
2399.33

Em nosso bloco END, usamos a construo "for (x in globcat)" para iteragir atravs de todas as
categorias, fazendo um balano mestre baseado em todas as transaes registradas. Na verdade fazemos
dois balanos, um para fundos disponveis, e outro para o balano da conta. Para executar o programa e
processar seus prprios dados financeiros que voc tenha inserido em um arquivo chamado
"mychecbook.txt", coloque todo o cdigo acima em um arquivo texto chamado "balance", faa "chmod
+x balance", e ento escreva "./balance mycheckbook.txt". O script de balano ir ento somar todas as
transaes e escrever um balano resumido de duas linhas para voc.

Atualizaes
Eu uso uma verso mais avanada deste programa para administrar minhas prprias finanas pessoais.
Minha verso (que eu no posso incluir aqui devido a limitaes de espao) escreve um resumo mensal de
entradas e despesas, incluindo totais anuais, resumo de entradas e vrias outras coisas. Melhor ainda, ele
escreve os relatrios em formato HTML, de forma que eu posso olhar o mesmo no meu browser web :)
Se voc acha que este programa til, eu encorajo voc a acrescentar estas funcionalidades ao seu script.
Voc no precisa configurar o mesmo para registrar nenhuma informao adicional, todas as informaes
que voc precisa j esto em balance e balance2. Basta atualizar o bloco END, e voc est com tudo!
Espero que voc tenha gostado desta srie. Para maiores informaes sobre o awk, veja a lista de recursos
abaixo.

Recursos

Leia o Awk em exemplos, Parte 1 e Awk em exemplos, Parte 2.


Se voc do tipo que prefere um livro, o sed & awk, 2nd edition uma escolha excelente.
Certifique-se de checar o FAQ do comp.lang.awk. Ele tambm muitos links adicionais sobre o
awk.
O awk tutorial, de Patric Hartigan, vem com muitos scripts awk teis.
O Thompson's TAWK Compiler, compila scripts awk em executveis binrios bem rpidos.
Existem verses disponveis para Windows, OS/2, DOS e UNIX.
O GNU Awk User's Guide est disponvel como referncia online.

Sobre o autor
Residindo em Albuquerque, New Mexico, Daniel Robbins o Presidente/CEO da Gentoo Technologies,
Inc., o criador do Gentoo Linux, um Linux avanado para o PC, e o sistema Portage, a prxima gerao
de sistema de ports para o Linux. Ele tambm tem servido como autor para os livros da Macmillan
Caldera OpenLinux Unleashed, SuSE Linux Unleashed, e Samba Unleashed. Daniel est envolvido com
computadores de alguma forma desde o segundo grau, quando foi exposto pela primeira vez para a
linguagem de programao Logo, bem como a uma dose perigosa de Pac Man. Isto provavelmente
explica por que ele tem trabalhado como Lead Graphic Artist na SONY Electronic
Publishing/Psygnosis. Daniel gosta de gastar seu tempo com sua esposa, Mary, e sua nova filhinha,
Hadassah. Voc pode entrar em contato com Daniel no email drobbins@gentoo.org.

Você também pode gostar