Este segundo episódio da série apresenta um one liner
que imprime o ranking de meses com mais artigos publicados
em um blog (e uma explicação detalhada de como o one liner
foi construído, para que o leitor conheça mais as ferramentas
que qualquer distribuição Linux nos dá de graça e muitas vezes
nem sabemos que existem).
Este script funciona apenas para blogs que tenham uma listagem de
artigos mais publicados com a seguinte estrutura geral (esse padrão
pode se repetir várias vezes):
mês ano_com_quatro_dígitos (quantidade_de_artigos)
Para este artigo utilizo como exemplo o blog da Thânia Clair,
pois se eu utilizar o meu, após publicado o artigo as expressões
regulares utilizadas passam a casar com as listagens deste artigo,
e todos os exemplos passam a apresentar resultados diferentes
dos que eu obtive antes da publicação.
Eis o one liner:
w3m -dump http://www.thaniaclair.com | awk '/[0-9][0-9][0-9][0-9] \([0-9]+\)$/ { print $4, $2, $3 }' | sed 's/[\(|\)]//g' | sort -gr
Eis o resultado:
23 Janeiro 2008
18 Dezembro 2007
16 Fevereiro 2008
13 Novembro 2007
6 Abril 2008
5 Março 2008
2 Maio 2008
1 Junho 2008
Não parece muito simples de entender, né? Não se preocupe, vou
apresentar aqui, passo a passo, como cheguei a essa solução. Considero
esse um exemplo significativo de one liner, pois demonstra a filosofia que
tenho seguido para criar minhas soluções: escolher a ferramenta mais
adequada para a tarefa. Note que utilizei 4 programas para chegar a esse resultado,
em vez de uma única linguagem de programação, ao contrário do que nos ensinam
em cursos Brasil afora.
Estes 4 programas têm propósitos bem específicos, e por isso atendem tão bem ao
domínio do meu problema:
w3m: navegador web modo texto, com suporte a screen scraping
awk: linguagem criada para processamento de dados estruturados
sed: linguagem criada para transformação de texto
sort: programa criado para ordenar linhas de um arquivo
Como sugestão, proponho ao leitor realizar a mesma tarefa com uma linguagem
de programação de propósito geral, como Perl, PHP, Python ou Ruby, e depois me contar em quantas
linhas resolveu o mesmo problema (ouso chutar que Ruby e Perl gastariam menos linhas).
Só pra não perder a deixa: tente em Java também!
Obviamente, não tenho nada contra nenhuma dessas linguagens, estou
apenas reforçando a minha fisolofia da melhor ferramenta para o trabalho.
Agora vamos à solução.
Passo 1 - Imprimir o conteúdo do site
Para imprimir o conteúdo de um site na saída padrão a opção mais simples que eu
conheço é utilizar ou o lynx ou o w3m com a opção -dump habilitada. O conteúdo
é impresso em um formato estruturado, mas todas as tags são removidas. O seguinte
comando:
w3m -dump http://www.thaniaclair.com
Imprimiria um documento que terminaria assim (estou suprimindo caracteres
especiais que só fazem sentido quando lidos em um terminal):
ruby (6)
tirinhas (21)
web (1)
Arquivos
Junho 2008 (1)
Maio 2008 (2)
Abril 2008 (6)
Março 2008 (5)
Fevereiro 2008 (16)
Janeiro 2008 (23)
Dezembro 2007 (18)
Novembro 2007 (13)
Nosso próximo passo é filtrar esse documento, para trabalhar apenas com as linhas que
nos interessam. Nesse ponto entra o awk.
Passo 2 - Selecionar as linhas que interessam com awk
As linhas que nos interessam são todas aquelas que exibem a quantidade de artigos
publicados durante cada mês de vida do blog. Essas linhas seguem o mesmo padrão:
o nome de um mês, seguido de um espaço em branco, seguido de um ano, seguido de um
espaço em branco, seguido pelo número de artigos entre parênteses.
A função do awk, nesse momento, é varrer todo o documento impresso pelo w3m, e
desse documento excluir todas as linhas que não se enquadrem nesse padrão. Nossa
declaração (na forma de uma expressão regular) é a seguinte: imprima qualquer linha
que termine com 4 números (o ano), seguido por um espaço em branco, seguido por
um ou mais números (a quantidade de artigos) cercados por parênteses, caso tudo
isso esteja no fim da linha.
awk '/[0-9][0-9][0-9][0-9] \([0-9]+\)$/ { print $0 }'
O awk, então, imprimiria:
Junho 2008 (1)
Maio 2008 (2)
Abril 2008 (6)
Março 2008 (5)
Fevereiro 2008 (16)
Janeiro 2008 (23)
Dezembro 2007 (18)
Novembro 2007 (13)
Ótimo! Apenas as linhas que nos interessam. Mas como vamos ordenar esses dados?
Como sabemos de antemão que o programa que irá ordená-los será o sort
(e que o sort ordena as linhas lendo-as da esquerda para a direita), temos
que posicionar a quantidade de artigos do mês no início da linha, e não no final.
Nosso texto precisa, portanto, de transformação. Mas essa transformação é trivial
para o awk. Vamos ver porquê.
No momento em que o awk encontra uma linha que atende ao padrão
que especificamos ele define um conjunto de variáveis iniciadas por $. $0
corresponde à própria linha; para definir as demais variáveis $ o awk
utiliza (por padrão) o espaço em branco como caracter divisor de dados, e atribui cada dado a
uma variável, da esquerda para direita, de 1 até N (N = quantidade de dados). No
nosso exemplo, nossas linhas geram as seguintes variáveis:
$1: um caracter especial gerado pelo w3m que eu decidi ignorar
$2: o mês
$3: o ano
$4: a quantidade de artigos publicados, cercados por parênteses
Nosso programa awk anterior, portanto, deve ser modificado da seguinte forma:
awk '/[0-9][0-9][0-9][0-9] \([0-9]+\)$/ { print $4, $2, $3 }'
Nossa saída, agora, seria:
(1) Junho 2008
(2) Maio 2008
(6) Abril 2008
(5) Março 2008
(16) Fevereiro 2008
(23) Janeiro 2008
(18) Dezembro 2007
(13) Novembro 2007
Ótimo! Agora nossos dados estão quase prontos para serem passados ao sort. O problema
são os parênteses cercando a quantidade de artigos. Se passarmos os dados dessa forma
para o sort ele não realizará a ordenação corretamente. Precisamos, portanto, realizar
uma simples substituição em todas as linhas: eliminar os 2 parênteses. Poderíamos tentar
fazer isso diretamente no awk, mas certamente o código-fonte perderia legibilidade, e talvez
fosse obrigado a deixar de ser um one liner (e tirar todo o propósito deste artigo!).
A saída, portanto, é escolher a melhor ferramenta para o trabalho: sed.
Passo 3: Eliminar caracteres desnecessários
Este simples comando sed irá substituir os parênteses de abertura e fechamento por
uma string vazia em todas as linhas do texto que chegar como entrada:
sed 's/[\(|\)]//g'
Nossa entrada, que era assim (apenas as 2 primeiras linhas):
(1) Junho 2008
(2) Maio 2008
Sairá do sed assim:
1 Junho 2008
2 Maio 2008
Com essa saída o sort é capaz de fazer a tarefa dele: ordenar as linhas.
Passo 4: Ordenar as linhas
Por padrão o sort ordena as linhas da mesma forma que um dicionário ordena
seus verbetes. Assim, por exemplo, 10 apareceria antes de 9. Para instruir
o sort a ordenar as linhas de entrada como se elas fossem dados numéricos,
utilizamos a opção -g, e para inverter a ordenação (colocando o maior número
no início e o menor no fim), utilizamos a opção -r. Nosso último filtro, portanto,
é o seguinte:
sort -gr
E quando aplicado à saída do programa sed:
1 Junho 2008
2 Maio 2008
6 Abril 2008
5 Março 2008
16 Fevereiro 2008
23 Janeiro 2008
18 Dezembro 2007
13 Novembro 2007
Nos devolve:
23 Janeiro 2008
18 Dezembro 2007
16 Fevereiro 2008
13 Novembro 2007
6 Abril 2008
5 Março 2008
2 Maio 2008
1 Junho 2008
Problema resolvido!
Comentários adicionais
É importante ressaltar que se, ao fim do passo 1, a quantidade de artigos
publicados no mês estivesse aparecendo na primeira coluna das linhas, não
seria necessário utilizar o awk no passo 2, pois para filtrar linhas de um arquivo
basta utilizar o programa grep.
Também seria possível, após o passo 4, fazer com que a quantidade de artigos
publicados no mês voltasse a ser a terceira coluna de cada linha se adicionássemos
mais uma chamada ao awk:
awk '{ print $2, $3, $1 }'
E se quiséssemos restaurar os parênteses, então o programa acima se tornaria:
awk '{ print $2, $3, "("$1")" }'
Agora você já deve entender o que esses dois programas fazem.