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

quarta-feira, 18 de junho de 2008

Tabela do Brasileirão 2008 com links + sed

O sed é um processador de texto muito versátil.

O links é um browser modo texto com features tão interessantes quanto do antigo lynx.

Ano passado eu postei como imprimir a tabela do brasileirão com um one liner interessante. Agora eu resolvi beber de outra fonte:

#!/bin/bash
TIME=Internacional
COLOR=$(echo -ne '\e[31;1m&\e[m')
URL=http://globoesporte.globo.com/Esportes/Futebol/Classificacao/0,,ESP0-9827,00.html
links --dump ${URL} | sed -r 's/\[[0-9]*\]//;/(^[0-9]|J jogos)/!d' | sed "s/${TIME}/${COLOR}/
1a-----Libertadores----------
4a-----Pre-Libertadores------
5a-----Sul-Americana---------
13a---------------------------
17a-----Rebaixado-------------"


O resultado não é tão bom quanto o do ano passado, é verdade, mas não deixa de ser uma alternativa

quarta-feira, 28 de maio de 2008

Pesquisando nos Grupos do Yahoo

Meu amigo MrBits me deu essa grande ideia:

#!/bin/bash
QTDE=20
CHARSET=UTF-8
GROUP=shell-script

while getopts g:c:q: OPTION ; do
case $OPTION in
g) GROUP=$OPTARG ;;
c) CHARSET=$OPTARG ;;
q) QTDE=$OPTARG ;;
esac
done

shift $(($OPTIND -1))

SEARCH="$@"

if [ -z "$SEARCH" ]; then
echo "Usage: $(basename $0) [-q qtde] [-g grupo] [-c charset] search"
exit 1
fi

BASEURL="http://br.groups.yahoo.com/group"
URL="${BASEURL}/${GROUP}/msearch?submit=OK&charset=${CHARSET}&cnt=${QTDE}&query=${SEARCH// /+}"
SEDCMD="/message/!d;/${GROUP}/!d;/.*[0-9]/!d;s#/group#${BASEURL}#g;s/<[^>]*span>//g;s/$/<br \/>/"

LYNXOPT="-dump -force_html -assume_charset=${CHARSET}"
lynx $LYNXOPT <( curl -b /tmp/cookie$$ -sL "${URL}" | sed "${SEDCMD}" )


Vejamos em uso:
$ ./find.sh              
Usage: find.sh [-q qtde] [-g grupo] [-c charset] search

$ ./find.sh -q 10 getopts # procurando por getopts limitando a 10 registros
[1]Re: [shell-script] Script de busca de mensagens
[2]Re: [shell-script] Re: script iterativo / passo a passo
[3]Re: [shell-script] Script de Backup
[4]Re: [shell-script] Script de Backup
[5]Re: [shell-script] Script de Backup
[6]Re: [shell-script] Script de Backup
[7]Re: [shell-script] Echo e executa: superecho
[8]Re: [shell-script] testar se parametro $1 foi passado
[9]Re: [shell-script] Echo e executa: superecho
[10]Re: [shell-script] testar se parametro $1 foi passado

References

1. http://br.groups.yahoo.com/group/shell-script/message/26373
2. http://br.groups.yahoo.com/group/shell-script/message/26204
3. http://br.groups.yahoo.com/group/shell-script/message/26078
4. http://br.groups.yahoo.com/group/shell-script/message/26076
5. http://br.groups.yahoo.com/group/shell-script/message/26075
6. http://br.groups.yahoo.com/group/shell-script/message/26072
7. http://br.groups.yahoo.com/group/shell-script/message/25742
8. http://br.groups.yahoo.com/group/shell-script/message/25720
9. http://br.groups.yahoo.com/group/shell-script/message/25719
10. http://br.groups.yahoo.com/group/shell-script/message/25718


Divertido, não? o default é procurar no grupo shell-script do yahoo, mas ele pode investigar em qualquer um.

Valeu Mr.Bits!!

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

sexta-feira, 28 de março de 2008

Hackeando um Hello World com sed

Fantástico o que o sed pode fazer!

$ cat a.c
main(){
puts("hello world");
}
$ gcc a.c
$ ./a.out
hello world
$ sed -i 's/hello world/_ola mundo_/'./a.out
$ ./a.out
_ola mundo_

sexta-feira, 22 de fevereiro de 2008

Alterando Multiplos Arquivos (versão final?)

O site Dicas-L trouxe na ultima semana 2 formas de renomear multiplos arquivos (aqui e aqui)

#Forma 1
for o in $(ls -1 *.txt); do
mv $o $(echo $o | awk -F. '{print $1".htm"}');
done


#Forma 2
for i in `ls *.txt`; do
mv $i $(echo `basename $i .txt`.html)
done


São ambas formas interessantes, porém ao meu ver consomem muito recursos da maquina, sem falar que são um tanto... feios... (nada contra - o que importa é que funcione)

Vou utiliza-los como exemplo de como podemos tornar algo melhor e mais prático (se o tempo permitir).

Vamos dividir as paradas:

altera() { mv $1 $(echo `basename $1 .txt`.html) ; }

for i in `ls *.txt`; do
altera $i
done


Agora vamos tomar um cuidado: arquivos com espaço no nome

altera() { mv "$1" $(echo `basename "$1" .txt`.html) ; }

for i in `ls *.txt`; do
altera "${i}"
done


Bom, o for pode iterar sobre uma lista de argumentos. As mascaras de nome de arquivo são expandidos pelo shell durante a execução, logo...

for i in *.txt ; do
altera "${i}"
done


Agora, a rotina de alteração do nome do arquivo de destino é complicadissima, depende de um ou mais sub-processos. Isso poderia ser...

altera() { mv "$1" "${1%.txt}.html" ; }


Que, inserido no for...

for i in *.txt ; do
mv "${i}" "${i%.txt}.html"
done


Interessante, certo? Nenhum sub-processo, exceto o inumeros mv que serão executados. Existem outras formas de fazer a mesma coisa

ls *.txt | awk -F. -v OFS=. '{ O=$0; $NF="html" ;printf "\"%s\" \"%s\"\n",O,$0 }' | xargs -n 2 mv

ls *.txt | sed 's#^\(.\+\)\.[^.]\+$#"&" "\1.html"#g' | xargs -n 2 mv


(rodem as linhas acima sem o mv do xargs para entende-las -- é metaprogramação)

Agora... tudo isso é muito bonito mas... veja se o seu computador possui os comandos mmv ou rename (que facilitam Absurdamente a tarefa)

rename .txt .html *.txt

mmv "*.txt" "#1.html"


Simples, não?

Tudo depende do tempo que temos e das nossas necessidades. Mesmo que o rename/mmv sejam uteis, pode ser que a forma com awk / sed valha mais a pena pois o ls pode ser substituido por um find (ja pensou nisso?)

quarta-feira, 30 de janeiro de 2008

Twitter.sh versão 2.0

O Bruno me deu uma ótima sugestão: um simples usage.

Entretanto ontem eu fiquei batendo cabeça com os inúmeros tac/tail's mas percebi q um sed resolvia a questão. Alias é possivel também passar uma lista de parâmetros também!

#!/bin/bash
TWIURL=http://m.twitter.com # utilizando versão 'mobile' do twitter
USAGEMSG="Usage: $(basename $0) <twitter-user> [user list...]"

[ -z "$1" ] && { echo "${USAGEMSG}" ; exit 1 ; }
for i in "$@" ; do
echo "==============================="
links -dump "${TWIURL}/${i}" | sed -r '1,3d;/^\ +Older [0-9]+/,$d'
done

Agora chega :)

quarta-feira, 31 de outubro de 2007

AWK e Arrays Associativos

Um grande problema para quem trabalha com arrays associativos no AWK é
com relação a ordem dos elementos quando esta fazendo uma iteração.

Ex:

$ gawk 'BEGIN{ split("5 4 2 1 3",T) ; for(i in T) print T[i]}'
1
3
5
4
2


Eu inseri, nessa ordem, 5, depois 4, depois 2... porém o acesso aos
elementos de um array em um for do tipo
for( variavel in array)

é imprevisive / incontrolável.

Agora vejamos isto:

$ WHINY_USERS=1 gawk 'BEGIN{ split("5 4 2 1 3",T) ; for(i in T) print T[i]}'
5
4
2
1
3


WOW! Através da variavel de ambiente do unix WHINY_USERS agora eu consigo acessar os elementos na ordem em que eles foram inseridos no array!

E para acessar os valores na ordem crescente:
$ WHINY_USERS=1  gawk 'BEGIN{ split("5 4 2 1 3",T)
asort(T) ; for(i in T) print T[i] }'
1
2
3
4
5


A variavel de ambiente WHINY_USERS não esta documentada no man, muito menos na documentação que consegui a respeito do gawk. Descobri esta dica no comp.lang.awk e, por incrivel que pareça, tive que vasculhar o codigo fonte do gawk (graças a deus podemos fazer isso) para entender melhor o que essa variavel poderia fazer.

Enfim, fica a dica.

segunda-feira, 1 de outubro de 2007

Convertendo Números

Um dos meus primeiros desafios na área de programação foi fazer um conversor de números decimais para romanos. Eu estava aprendendo Turbo Pascal e usava um potente 286 com 1MB de memória - sem HD.

Fiz a maior sequencia de ifs da minha vida. Hoje estava relembrando e resolvi fazer uma versão SED.

/[0-9]*[5-9]...$/q
s/1...$/M&/;s/2...$/MM&/;s/3...$/MMM&/;s/4...$/MMMM&/
s/6..$/DC&/;s/7..$/DCC&/;s/8..$/DCCC&/;s/9..$/CM&/
s/1..$/C&/;s/2..$/CC&/;s/3..$/CCC&/;s/4..$/CD&/;s/5..$/D&/
s/6.$/LX&/;s/7.$/LXX&/;s/8.$/LXXX&/;s/9.$/XC&/
s/1.$/X&/;s/2.$/XX&/;s/3.$/XXX&/;s/4.$/XL&/;s/5.$/L&/
s/1$/I/;s/2$/II/;s/3$/III/;s/4$/IV/;s/5$/V/
s/6$/VI/;s/7$/VII/;s/8$/VIII/;s/9$/IX/
s/[0-9]//g


Tendo um número por linha ja basta.

$ echo '3999' | sed -f other.sed
MMMCMXCIX


O sed é realmente fantástico! Pena que acima de 5 mil tem que colocar uma barra em cima dos números.

terça-feira, 28 de agosto de 2007

Metaprogramação com awk e sed

Criar um programa que cria programas é simples nas linguagens interpretadas.

Criamos o programa em uma string e executamos via eval() ou então gravamos em arquivo e executamos novamente.

Me deparei com o seguinte problema: inverter as palavras de uma frase ou arquivo mantendo a ordem em que aparecem.

É claro que eu pensei em usar o rev, mas ele inverte a linha como um todo. Depois de fazer um laço for muito feio em awk, fiquei pensando em como resolver de forma mais legível.

Tive esta ideia: vou fazer uma lista de palavras, inverte-las com o rev e, para cada palavra, vou substitui-la pela palavra invertida. Beleza, o sed faz isso com um pé nas costas.

Eu tenho um arquivo (poderia ser um named pipe) chamado 'direito' que contem uma lista de palavras sem repetição, com uma palavra por linha. Outro arquivo, com o mesmo conteudo mas revertido via 'rev'.

Uso o paste para colocar os arquivo lado a lado e uso o awk para gerar comandos como este:

s/\bpalavra\b/palavra_revertida/g;


Usei o awk pq a sintaxe fica mais clara, o sed ficou muito poluído. Perceba que eu uso o recurso de borda das expressões regulares. Isso me garante que vou trocar uma palavra inteira, e não um pedaço da string.

Agora vem o pulo do gato: mando estes comandos via stdin para o sed, fazendo uso de um pipe. eu informo para o sed que os comandos virão pela stdin passando a opção -f -

Vejam o resultado abaixo, espero que seja útil para alguem :)

$ cat stuff
Nosso fórum principal.

Problemas com hardware em geral,

temperaturas, comparação de desempenho,

compatibilidades de componentes, etc.

$ LC_ALL=pt_BR grep -oE '\w+' stuff | sort -u | tee direito | rev > reverso

$ paste direito reverso | awk '{
printf "s/\\b%s\\b/%s/g;\n",$1,$2 # facil, não?
}' | sed -f - stuff
ossoN muróf lapicnirp.

samelborP moc erawdrah me lareg,

sarutarepmet, oãçarapmoc ed ohnepmesed,

sedadilibitapmoc ed setnenopmoc, cte.

Como o arquivo possui acentos, precisei setar a variavel LC_ALL para pt_BR, caso contrario a expressão regular \w+ não iria casar com todas as palavras.

Ps: Julio, que tal chamar isso de "Inversor do Tiago"?

Extended Globbing

Não sei se é conhecimento de todos mas o Bash possui, alem do globbing normal (a expansão * ? e [a-z] de nomes de arquivos e diretórios), um globbing extendido.

Acho que, em alguns casos, podera ser BEM util, eliminando um pipe para um grep por exemplo.

São eles (direto do man)

?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
@(pattern-list)
Matches exactly one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns

Para poder utiliza-lo precisa executar o shopt conforme o exemplo abaixo

$ shopt -s extglob

$ ls
file filename filenamename fileutils

$ ls file?(name) # padrão pode aparecer 0 a 1 vezes

file filename

$ ls file*(name) # padrão pode aparecer 0 a N vezes
file filename filenamename

$ ls file+(name) # padrão pode aparecer 1 a N vezes
filename filenamename

$ ls file@(name) # padrão deve aparecer 1 unica vez
filename

$ ls file!(name) # divertido esse! padrão NÃO deve aparecer
file filenamename fileutils

$ ls file+(name|utils) # cada padrão pode aparecer 1 a N vezes
filename filenamename fileutils

$ ls file@(name|utils) # "lembra" um file{name,utils} ;-)
filename fileutils

terça-feira, 7 de agosto de 2007

Google Wars

Inutilidade Pública: veja o que/quem possui mais resultados no google?

./GoogleWars.sh  superman batman
batman 48500000
superman 33700000

./GoogleWars.sh gremio inter
inter 213000000
gremio 5560000

./GoogleWars.sh hp ibm
hp 379000000
ibm 147000000

./GoogleWars.sh cobol java fortran perl ruby lisp haskell
java 272000000
perl 103000000
ruby 101000000
lisp 21400000
fortran 17400000
haskell 15400000
cobol 10900000

O script é simples, abusando um pouco do SED e do AWK, com um toque de SORT ;-)

#!/bin/bash
COR=`echo -ne '\e[31;1m'`
END=`echo -ne '\e[m'`
CORSED="1s/.*/${COR}&${END}/"
GOOGLE='http://www.google.com/search'

[[ $2 ]] || { echo "usage: $0 item item2 [ ... item n]" ; exit 0 ; }

SEDCMD='/did not match any documents/{s/.*/0/g;p;q}
/Results/{s/.* about[ ]\+\([0-9,.]\+\).*/\1\n/;p;q}'
AWKCMD='{ result[$1]=$2 ; x = length($1) ; if(x > max) max=x }
END{ for(i in result) printf "%-"max"s\t%s\n",i,result[i]}'

for i in "$@" ; do echo -ne "${i}\t"
lynx -dump "${GOOGLE}?q=${i}" | tr -d '\n.,' | sed -n "${SEDCMD}"
done | awk -F '\t' "${AWKCMD}" | sort -nr -t $'\t' -k 2 | sed "${CORSED}"


Quem quiser melhora-lo, sinta-se à vontade :)

Edit: ja lancei uma versão .02 com umas correções cosméticas aqui -- não resisti...

segunda-feira, 6 de agosto de 2007

Considerações de Segurança

Me irrita profundamente usar um script que pressupõe certas definições no meu ambiente sem aviso prévio. O caso mais comum é, em algum *nix, acreditar que o diretório corrente faz parte do PATH -- contrariando uma regra básica de segurança, inclusive.

Não é dificil fazer ./programa (na verdade com o tempo isso vira padrão até). Fica uma dica importante: nunca façam suposições a respeito do ambiente, variaveis, estruturas de diretórios sem documentar ou fazer os testes necessários (pelo menos quando for fora de algum padrão).

sexta-feira, 3 de agosto de 2007

Caçadores da Classe Perdida

Eu já tinha feito algo parecido mas este post do Claudio Miranda reavivou a minha memória.

#!/bin/bash
[[ $2 ]] || { echo "Uso: ${0} diretorio classe" ; exit 1 ; }
find "${1}" -name \*.jar -print | xargs -n 1 unzip -l 2>&- | awk -v class="${2}" '
BEGIN { IGNORECASE=1 }
/^Archive/ { file="\nArquivo:\n\t"$NF": \nClasses:\n\t" }
/class$/ && $NF ~ class { print file,$NF ; file="\t"; total++ }
END { print "\nTotal",total + 0,"classes encontradas!" }'


Informando como parâmetros um diretório de partida e um fragmento do nome da classe, o find localizará todos os arquivos com extensão jar e vai mandar pro nosso amigo xargs. Este envia um a um para o unzip que lista (-l) o conteúdo de cada arquivo. Eu poderia procurar a classe que eu quero com o grep, mas o awk foi a escolha para formatar melhor a saída de dados.

Perceba que a variavel IGNORECASE tem valor diferente de 0, assim eu posso procurar por um trecho do tipo xml ou XML. Eu preciso saber o nome do arquivo 'corrente' e isso é informado pela linha que começa com 'Archive'. Quando eu encontro uma linha que termina com 'class' e o ultimo campo ( $NF ) 'casa' com o fragmento de nome (alias pode ser uma expressão regular!) eu imprimo esta linha.

Eu fiz uma sacanagem pra saída de dados ficar 'bonitinha', que é imprimir o 'cabeçalho' apenas uma vez, depois eu troco por tab (\t) -- e no final eu mostro um sumário com o número de referências encontradas.

Este script pode ser modificado para outros propósitos. Note que eu procuro tanto no nome da classe quando no nome dos pacotes (diretórios), assim xml poderia casar com XMLHelper.class ou /java/xml/foobar.jar -- mas basta ser criativo para resolver isso!

quinta-feira, 2 de agosto de 2007

Usando o curl com Endereços IPv6

Hoje em dia usar endereçamento IPv6 parece um pouco exótico, porém aos poucos ele começa a aparecer em algumas redes internas e logo será a unica saída quando todos os 32 bits do IPv4 estiverem em uso, previsto para 2012.

Diga adeus ao "206.45.32.234" - longa vida ao "2001:bce4:5641:3412:341:45ae:fe32:65". Perceba como agora utilizamos a representação hexadecimal e cada quarteto é separado por um ':' -- o que torna mais fácil a sua memorização (e viva o DNS). Um bom artigo introdutório pode ser lido aqui.

Pois bem, imagine agora que precisamos acessar uma pagina em um servidor (por enquanto interno) através de um endereço IPv6. A url ficaria assim, por exemplo:

http://[2001:bce4:5641:3412:341:45ae:fe32:65]:8000/index.php


Como o caracter ':' é usado em uma url para separar o servidor da porta (é claro que isso é omitido na maioria dos casos, quando usamos a porta 80), e tiveram que proteger o ip entre [colchetes]. Um browser como o Firefox consegue entender esta url facilmente, entretanto não tive a mesma sorte ao usar os browsers modo texto mais comuns - lynx e o curl.

Não subestime a importância destes browsers! Muitas vezes nos encontramos em um sistema com poucos recursos ou precisamos efetuar certas ações via algum script e eles caem como uma luva -- principalmente o curl, que simplesmente transfere informações de/para um servidor web, fazendo GET, POST, HEAD, upload de arquivos, etc.

No man do curl descobri que o mesmo depende da libcurl ser capaz de resolver endereços ipv6 - inclusive posso forçar a usar apenas ipv6 com a opção -6.

Entretanto o curl tem outra interpretação dos colchetes em uma url -- elas são um recurso de sequência para multiplos downloads (da mesma forma como o bash faz, porém é interno ao curl), como nesse exemplo:

curl 'http://www.any.org/archive[1996-1999]/volume[1-4]part{a,b,c,index}.html'


Para desabilitar este "URL globbing parser" basta usar a opção -g, o que nos leva a solução completa:

curl -6g 'http://[2001:bce4:5641:3412:341:45ae:fe32:65]:8000/index.php'


Caso não queria desabilitar o globbing, basta 'escapar' os colchetes usando a contra-barra antes. O curl é um programa extremamente versátil, um verdadeiro canivete suíço na linha de comando, vale a pena estuda-lo em algum tempinho livre.

quinta-feira, 26 de julho de 2007

Resolvendo Problemas Comuns 7 - open failed: |

Imagine um arquivo com algumas linhas duplicadas:

$ cat arquivo
permission denied
bad interpreter
missing separator
set correct localle
parameter list too long
unary operator expected
parameter list too long
bad interpreter


Obter as linhas distintas pode ser feito com a ajuda do sort + uniq

$ cat arquivo | sort | uniq
bad interpreter
missing separator
parameter list too long
permission denied
set correct localle
unary operator expected


- Puxa! Que ótima combinação! Vamos guarda-la?

Bom, podemos querer guardar este e outros comandos em uma variável de ambiente também, afinal, deve funcionar sem maiores problemas, certo?
$ CMD="sort | uniq"
$ cat arquivo | $CMD
sort: open failed: |: No such file or directory


- OPA! Eu sabia, maldito shell, encrenca com tudo!

Tsc... isso acontece porque o pipe | não foi informado como um 'pipe' e sim como a string "|" -- e o sort não conseguiu abrir o arquivo "|".
Este tipo de problema pode ser resolvido pelo eval -- ele interpreta novamente as strings na linha de comando.
$ eval "cat arquivo | $CMD"
bad interpreter
missing separator
parameter list too long
permission denied
set correct localle
unary operator expected


- Ah, agora funciona. Mas esse eval ai ficou feio...

Então vamos criar um alias para o comando!
$ alias cmd="sort | uniq"
$ cat arquivo | cmd
bad interpreter
missing separator
parameter list too long
permission denied
set correct localle
unary operator expected


Perceba que o comportamento do alias é diferente de uma variavel de ambiente. Devemos ficar atento à correta interpretação da nossa linha de comando pelo shell corrente, senão vamos ter resultados nem sempre amigáveis.

Ah, sim, o cat nesse exemplo é completamente supérfluo, poderia ser assim: 'sort arquivo | uniq'

- E o uniq precisa do sort ?

Sim... sem o sort ele fatalmente vai se perder, e é muito mais facil eliminar linhas repetidas que estejam em sequencia do que aleatórias. É bom dar uma lida no man destes comandos pois possuem opções muito poderosas!

Resolvendo Problemas Comuns 6 - unary operator expected

Uma desatenção, muito comum quando trabalhamos com variáveis de ambiente no shell é que, quando elas não foram setadas ainda, o shell expande elas para... nada.

$  [ $XALALA == "oi" ] && echo 'ok' || echo 'nok'
-bash: [: ==: unary operator expected
nok


Nesse caso o (resultado do) teste da variável até pode fazer sentido, porém troque o == por um != e veja o resultado. A solução é proteger a variável com aspas!

$  [ "$XALALA" != "oi" ] && echo 'ok' || echo 'nok'
ok


Simples e rápido!

Resolvendo Problemas Comuns 5 - The parameter list is too long

Essa é raro, mas acontece

$ cd /meu/diretorio
$ rm *.dll
Error: The parameter list is too long


- O que? Eu não posso apagar todos os meus 3459834574935734957 arquivos?

Bom, primeiro vamos analisar o que aconteceu: a linha de comando 'rm *.dll' é expandida pelo shell corrente a 'rm arquivo1.dll arquivo2.dll ... arquivoN.dll' e, nesse caso, esta lista de parâmetros foi demais pro probrezinho do rm.

Com o xargs é facil de resolver!
$ find /meu/diretorio -name '*.dll' -print | xargs rm


O que ele faz? o xargs recebe pela stdin toda uma lista de parâmetros que será entregue ao programa rm. Se esta lista extender o limite do sistema, então o xargs, inteligentemente, irá executar o rm uma vez, com a lista que for possível e, depois, executar de novo, até que todos os parâmetros sejam lidos. Aliás o xargs possui muitas opções interessantes, vale a pena dar uma estudada nele.

E assim todas as dll's são apagadas -- em definitivo, pois com rm 'tr00' não tem undelete ;-)

Resolvendo Problemas Comuns 4 - Corrigindo o localle

Precisamos estar atento que as variáveis de ambiente de localização podem atrapalhar, e muito, o comportamento de algumas expressões regulares.

Por exemplo, se eu quero encontrar uma palavra que começe com a e termine com o (ex: ação)

$ cat arquivo
luz, camera, ação
$ grep -oE 'a\w+o' arquivo # nao mostra nada!

Antes de sair dizendo que o linux, grep ou as expressões regulares não prestam, vamos testar o mesmo comando no nosso 'idioma'
$ LC_ALL=pt_BR grep -oE 'a\w+o' arquivo
ação

Ahá! Agora vamos ver um exemplo mais interessante:
$ tr '[:lower:]' '[:upper:]' < arquivo
LUZ, CAMERA, AçãO
$ LC_ALL=pt_BR tr '[:lower:]' '[:upper:]' < arquivo
LUZ, CAMERA, AÇÃO

Perceberam como a variavel altera o comportamento das ERs?

Como ultima dica: sempre que precisarem comparar textos independente da 'caixa' (se maiúscula ou minúscula), usem os recursos de ignorecase da ferramenta utilizada. Converter um texto todo para minúscula pode não apenas ser afetado pela localização como existem idiomas onde alguns caracteres não tem o exato correspondente entre uma caixa e outra como nós pensamos -- é o caso do idioma Turco, onde o 'i' maiúsculo não é 'I'. E, principalmente, não tente fazer isso.

Mais informações em 'man locale' ;-)

Resolvendo Problemas Comuns 3 - missing separator

O make é um comando incrivelmente poderoso, principalmente para programadores, permitindo que tarefas sejam organizadas de acordo com dependências, por exemplo.

Um caso básico seria construir um grande programa, composto de diversos arquivos-fonte. O Makefile permite que vc compile apenas os arquivos que vc alterou desde o ultimo build, por exemplo, comparando o timestamp do fonte com o codigo objeto gerado.

Bom, ai um Makefile poderoso foi editado num editor de texto profissional e, na hora de testar, nos deparamos com:

$ make    
Makefile:3: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.

$ cat Makefile # nome padrão de arquivo para o make.
# um makefile comum
all:
@echo "oi mundo"


Puxa, parece tudo certo... entretanto ai vai um pequeno detalhe: para cada target do makefile, os comandos que vem a seguir devem começar por um TAB (alias a mensagem de erro é bem clara nesse sentido, certo?). Vamos conferir:

$ cat -A Makefile   # é mais pontente que o -v
# um makefile comum$
all:$
@echo "oi mundo"$


Tcharam! Se a linha do echo iniciasse com um TAB, como deveria ser, apareceria um ^I no inicio da linha. Provavelmente a origem disso é um editor de texto que transforma os TABS em 8 espaços por questões estéticas. Vamos corrigir o Makefile?

$ sed  -i 's/^\ \{8\}/\t/' Makefile
$ cat -A Makefile
# um makefile comum$
all:$
^I@echo "oi mundo"$
$ make
oi mundo


Pronto, basta substituir os espaços por TAB. Outra solução corrigir em um editor de textos mas sem a opção de expansão dos TABs -- no vim basta fazer

:set noexpandtab


e editar numa boa.