Mostrando postagens com marcador awk. Mostrar todas as postagens
Mostrando postagens com marcador awk. Mostrar todas as postagens

sábado, 26 de abril de 2008

Manipulando logs com AWK e SED

Eis que a lista de shell script traz um bom desafio.

Galera, tenho o seguinte log.:

AAAA-------------campo_1-------------campo_2-----campo_3----campo_4----------
teste_1 371508787 371547453 38666 testetesteteste

BBBB-------------campo_1-------------campo_2-----campo_3----campo_4----------
teste_2 4625081503 4651313710 26232207 testetesteteste

Estou a tentar usar o awk com a seguinte função :
awk '$1~"teste_" {print $5";"$4}' teste > teste_.csv

a funcao busca realmente o que desejo:
$5 $4
testetesteteste 38666
testetesteteste 6232207

porem,, gostaria que seprasse da forma:

AAAA-------------
testetesteteste 38666
BBBB-------------
testetesteteste 26232207

Alguém tem uma dica de como fazer?


Ah... o bom e velho SED pode resolver isso

$ sed -rn '/(^[^-]+-+).*/{s//\1/;h};
/^teste_/{s/.* ([^ ]+) +([^ ]+$)/\2 \1/;x;p;g;p}' arquivo.log
AAAA-------------
testetesteteste 38666
BBBB-------------
testetesteteste 26232207


Ok, ok, ta muito complicado, mas veja só:

$ sed -rn '/^[^-]+-+/h;/^teste_/{x;p;g;p}' arquivo.log
AAAA-------------campo_1-------------campo_2-----campo_3----campo_4----------
teste_1 371508787 371547453 38666 testetesteteste
BBBB-------------campo_1-------------campo_2-----campo_3----campo_4----------
teste_2 4625081503 4651313710 26232207 testetesteteste


Vamos explicar
1) a opção -n serve para informar ao sed "imprima apenas quando eu mandar"
2) a opção -p serve para utilizar expressões regulares extendidas
(assim não preciso escapar o quantificador + , que significa "um ou
mais vezes", assim como os parentesis, para informar os grupos).

Eu fiz uma sacanagem. o comando h quarda o padrão num espaço chamado espaço reserva, tipo uma memória do sed, sobreescrevendo. Assim no espaço reserva eu tenho a ultima ocorrencia de uma linha do tipo, ^[^-]+-+ ,que traduzindo significa: tudo o que começa com um ou varios caracteres diferentes de -, seguidos de um ou varios - (no caso
do AAAA------------- ... ).

Agora, quando eu encontro uma linha que começa com teste_ eu:

x) troco essa linha com a linha que esta na memória (a atual
'teste_...' vai, outra volta).
p) imprimo a linha que veio (AAAA---------- ...)
g) pego a linha da memória (teste_...)
p) imprimo a linha cachorrona

Só que não fica como vc quer. Ai vc precisa fazer a sacanagem:

se uma linha NÃO tem o que eu quero, então eu a manipulo habilmente
até que ela chegue ao que eu quero


Eu poderia ter usado varias tecnicas mas... uma vez com sed, podemos continuar nele.

$ sed -rn '/(^[^-]+-+).*/{s//\1/;h};
/^teste_/{s/.* ([^ ]+) +([^ ]+$)/\2 \1/;x;p;g;p}' arquivo.log


eu transformei a primera ER em (minha_ER).* -- ou seja, criei um grupo para o que me interessa. basta fazer:

s/(minha_ER).*/\1/

para que toda a linha seja reduzida ao que a minha ER casa. em outras palavras, eu apaguei o resto da linha.

na outra eu fui mais sacana pois eu tenho 2 grupos e troco toda a linha pelos grupos, na ordem inversa. coisa de quem toma muito café e não tem escrupulos.

Vamos ver a versão AWK?

$ awk '/^[^-]+-+/{match($0,/^[^-]+-+/); x=substr($0,1,RLENGTH)}
/^teste_/{print x,"\n"$5,$4}' arquivo.log
AAAA-------------
testetesteteste 38666
BBBB-------------
testetesteteste 26232207


x, nesse caso, armazena aquele pedaço da linha anterior, que eu descobri o que é via match. match procura uma expressão regular numa string, nesse caso em $0, e seta um valor na variavel RLENGTH, que é onde a expressão acaba. basta pegar essa parte da string e guardar na variavel x, que sera lida depois.

Aqui fala um pouco dessas duas funções: http://people.cs.uu.nl/piet/docs/nawk/nawk_92.html

Eu poderia ter resolvido dessa forma também
$ awk '/^[^-]+-+/{sub(/-[^-]+.*$/,"-");x=$0}
/^teste_/{print x,"\n"$5,$4}' arquivo.log
AAAA-------------
testetesteteste 38666
BBBB-------------
testetesteteste 26232207


Entretanto aqui eu faço uma substituição grosseira do resto da linha que tem o AAAA------... por -, abusando do .* (e o fato dele ser guloso). Parece mais simples, mas está sujeito à falhas, embora não consigo pensar em nenhuma situação que seja possivem demonstrar.

AWK & SED são ferramentas sensacionais para esse tipo de problema ;-)

domingo, 13 de abril de 2008

Um corretor ortográfico em gawk

Ano passado eu publiquei uma pequena nota sobre um pequeno corretor ortográfico feito em Python.

No artigo do Peter Norwig, ele explica o principio estatístico do algoritmo. No final, ele mostra varias implementações do algoritmo (em D, Java, Ruby e até Erlang).

Depois de muito pesquisar, decidi fazer uma versão em gawk. A primeira tinha 30 linhas e não funcionava muito bem, arrumando e testando cheguei a esta forma final com apenas 15 linhas.

Eu chamo de linha um statement completo do awk. Perceba que nenhuma linha dessas possui o separador de statement ; (ponto-e-virgula), exceto quando estou utilizando o for no estilo C.

# Usage: gawk -v word=something -f thisfile.awk [ big.txt [ big2.txt ... ]]
# Gawk version with 15 lines -- 04/13/2008
# Author: tiago (dot) peczenyj (at) gmail (dot) com
# Based on : http://norvig.com/spell-correct.html
function edits(w,max,candidates,list, i,j){
for(i=0;i< max ;++i) ++list[substr(w,0,i) substr(w,i+2)]
for(i=0;i< max-1;++i) ++list[substr(w,0,i) substr(w,i+2,1) substr(w,i+1,1) substr(w,i+3)]
for(i=0;i< max ;++i) for(j in alpha) ++list[substr(w,0,i) alpha[j] substr(w,i+2)]
for(i=0;i<= max ;++i) for(j in alpha) ++list[substr(w,0,i) alpha[j] substr(w,i+1)]
for(i in list) if(i in NWORDS) candidates[i] = NWORDS[i] }

function correct(word ,candidates,i,list,max,temp){
edits(word,length(word),candidates,list)
if (!asort(candidates,temp)) for(i in list) edits(i,length(i),candidates)
return (max = asorti(candidates)) ? candidates[max] : word }

BEGIN{ if (ARGC == 1) ARGV[ARGC++] = "big.txt" # http://norvig.com/big.txt
while(++i<=length(x="abcdefghijklmnopqrstuvwxyz")) alpha[i]=substr(x,i,1)
IGNORECASE=RS="[^"x"]+" }

{ ++NWORDS[tolower($1)] }

END{ print (word in NWORDS) ? word : "correct("word")=> " correct(tolower(word)) }


Veja o script em funcionamento:
$ time gawk -v word=somethink -f spelling.awk
correct(somethink)=> something

real 0m4.862s
user 0m4.702s
sys 0m0.093s