terça-feira, 5 de janeiro de 2010

Shell Script



Um shell script é um conjunto de comandos de terminal, organizados de forma a desempenhar alguma tarefa. O bash é extremamente poderoso, o que dá uma grande flexibilidade na hora de escrever scripts. Você pode inclusive incluir trechos com comandos de outras linguagens interpretadas, como perl ou python, por exemplo.


O primeiro passo para escrever um script é descobrir uma forma de fazer o que precisa via linha de comando. Vamos começar um um exemplo simples:





O comando "wget" permite baixar arquivos; podemos usá-lo para baixar o ISO do Kurumin, por exemplo:

$ wget -c http://fisica.ufpr.br/kurumin/kurumin-6.0.iso

(o "-c" permite continuar um download interrompido)

Depois de baixar o arquivo, é importante verificar o md5sum para ter certeza que o arquivo está correto:

$ md5sum kurumin-6.0.iso

Estes dois comandos podem ser usados para criar um script rudimentar, que baixa o Kurumin e verifica o md5sum. Abra o kedit ou outro editor de textos que preferir e inclua as três linhas abaixo:

#!/bin/sh

wget -c http://fisica.ufpr.br/kurumin/kurumin-6.0.iso

md5sum kurumin-6.0.iso

O "#!/bin/sh" indica o programa que será usado para interpretar o script, o próprio bash. Por norma, todo script deve começar com esta linha. Na verdade, os scripts funcionam sem ela, pois o bash é o interpretador default de qualquer maneira, mas não custa fazer as coisas certo desde o início. Existe a possibilidade de escrever scripts usando outros interpretadores, ou mesmo comandos, como o sed. Neste caso o script começaria com "#!/bin/sed", por exemplo.

Note que a tralha, "#", é usada para indicar um comentário. Toda linha começada com ela é ignorada pelo bash na hora que o script é executado, por isso a usamos para desativar linhas ou incluir comentários no script. A linha "#!/bin/sh" é a única exceção para esta regra.

Ao terminar, salve o arquivo com um nome qualquer. Você pode usar uma extensão como ".sh" para que outras pessoas saibam que se trata de um shell script, mas isto não é necessário. Lembre-se de que, no Linux, as extensões são apenas parte do nome do arquivo.

Marque a permissão de execução para ele nas propriedades do arquivo, ou use o comando:

$ chmod +x baixar-kurumin.sh

Execute-o colocando um "./" na frente do nome do arquivo, o que faz o interpretador entender que ele deve executar o "baixar-kurumin.sh" que está na pasta atual. Caso contrário ele tenta procurar nas pastas "/bin/", "/usr/bin" e "/usr/local/bin" que são as pastas onde ficam os executáveis do sistema e não acha o script.

$ ./baixar-kurumin.sh

O md5sum soma os bits do arquivo e devolve um número de 32 caracteres. No mesmo diretório do servidor onde foi baixado o arquivo, está disponível um arquivo de texto com o md5sum correto do arquivo. O resultado do md5sum do arquivo baixado deve ser igual ao do arquivo, caso contrário significa que o arquivo veio corrompido e você precisa baixar de novo.

Você já deve estar cansado de baixar as novas versões do Kurumin, e já sabe de tudo isso. Podemos aproveitar para ensinar isso ao nosso script, fazendo com que, depois de baixar o arquivo, ele verifique o md5sum e baixe o arquivo de novo caso ele esteja corrompido.

Isto vai deixar o script um pouco mais complexo:

#!/bin/sh

versao="6.0"

mirror=http://fisica.ufpr.br/kurumin/

wget -c "$mirror"/kurumin-"$versao".iso

md5sum=`md5sum kurumin-"$versao".iso`

wget -c "$mirror"/kurumin-"$versao".md5sum.txt

md5sumOK=`cat kurumin-"$versao".md5sum.txt`

if [ "$md5sum" != "$md5sumOK" ]; then

echo "Arquivo corrompido, vou deletar e começar novamente."

rm -f kurumin-"$versao".iso

sleep 120

./baixar-kurumin.sh

else

echo "O arquivo foi baixado corretamente."

fi

Você vai perceber que ao executar este segundo script, ele vai tentar baixar o arquivo novamente sempre que o md5sum não bater, se necessário várias vezes. Para isso, começamos a utilizar algumas operações lógicas simples, que lembram um pouco as aulas de pseudo-código que os alunos de Ciências da Computação têm no primeiro ano.

Em primeiro lugar, este segundo script usa variáveis. As variáveis podem armazenar qualquer tipo de informação, como um número, um texto ou o resultado de um comando. Veja que no início do script estou definindo duas variáveis "versao" e "mirror", que utilizo em diversas partes do script.

Ao armazenar qualquer texto ou número dentro de uma variável, você passa a poder utilizá-la em qualquer situação no lugar no valor original. A vantagem de fazer isso é que, quando precisar alterar o valor original, você só vai precisar alterar uma vez. O mesmo script poderia ser adaptado para baixar uma nova versão do Kurumin, ou para baixá-lo a partir de outro mirror simplesmente alterando as duas linhas iniciais. Usar variáveis desta forma permite que seus scripts sejam reutilizáveis, o que a longo prazo pode representar uma grande economia de tempo.

No script anterior, usamos o comando "md5sum kurumin-6.0.iso". Ele simplesmente mostra o md5sum do arquivo na tela, sem fazer mais nada. No segundo script, esta linha ficou um pouco diferente: md5sum=`md5sum kurumin-$versao.iso`. A diferença é que, ao invés de mostrar o mds5um na tela, armazenamos o resultado numa variável, chamada "md5sum".

O sinal usado aqui não é o apóstrofo, como é mais comum em outras linguagens, mas sim a crase (o mesmo do "à"). O shell primeiro executa os comandos dentro das crases e armazena o resultado dentro da variável, que podemos utilizar posteriormente.

Por padrão, os mirrors com o Kurumin sempre contém um arquivo ".md5sum.txt" que contém o md5sum da versão correspondente. Para automatizar, o script baixa também este segundo arquivo e armazena o conteúdo numa segunda variável, a "md5sumOK".

Neste ponto, temos uma variável contendo o md5sum do arquivo baixado, e outra contendo o md5sum correto. As duas podem ser comparadas, de forma que o script possa decidir se deve baixar o arquivo de novo ou não.

Para comparar duas variáveis (contendo texto) num shell script, usamos o símbolo "!=" (não igual, ou seja: diferente). Para saber se o arquivo foi baixado corretamente, comparamos as duas variáveis: [ "$md5sum" != "$md5sumOK" ].

Além do "!=", outros operadores lógicos que podem ser usados são:

= : Igual.

-z : A variável está vazia (pode ser usada para verificar se determinado comando gerou algum erro, por exemplo).

-n : A variável não está vazia, o oposto do "-z".

Estas funções permitem comparar strings, ou seja, funcionam em casos onde as variáveis contém pedaços de texto ou o resultado de comandos. O bash também é capaz de trabalhar com números e inclusive realizar operações aritméticas. Quando precisar comparar duas variáveis numéricas, use os operadores abaixo:

-lt : (less than), menor que, equivalente ao <.

-gt : (greather than), maior que, equivalente ao >.

-le : (less or equal), menor ou igual, equivalente ao <=.

-ge : (greater or equal), maior ou igual, equivalente ao >=.

-eq : (equal), igual, equivale ao =.

-ne : (not equal) diferente. Equivale ao != que usamos a pouco.

Mas, apenas comparar não adianta. Precisamos dizer ao script o que fazer depois. Lembre-se de que os computadores são burros, você precisa dizer o que fazer em cada situação. Neste caso temos duas possibilidades: o md5sum pode estar errado ou certo. Se estiver errado, ele deve baixar o arquivo de novo, caso contrário não deve fazer nada.

Usamos então um "if" (se) para criar uma operação de tomada de decisão. Verificamos o mds5um, se ele for diferente do correto, então (then) ele vai deletar o arquivo danificado e começar o download de novo. Caso contrário (else) ele vai simplesmente escrever uma mensagem na tela.

if [ "$md5sum" != "$md5sumOK" ]; then

echo "Arquivo corrompido, vou deletar e começar novamente."

rm -f kurumin-"$versao".iso

sleep 120

./baixar-kurumin.sh

else

echo "O arquivo foi baixado corretamente."

fi



Veja que dentro da função "then" usei o comando para deletar o arquivo e depois executei de novo o "./baixar-kurumin.sh" que vai executar nosso script de novo, dentro dele mesmo.

Isso vai fazer com que o script fique em loop, obsessivamente, até conseguir baixar o arquivo corretamente. Uma coisa interessante nos scripts é que eles podem ser executados dentro deles mesmos e alterados durante a execução. O script pode até mesmo deletar a si próprio depois de rodar uma vez, uma espécie de script suicida :-P.

É preciso tomar cuidado em situações como esta, pois cada vez que o script executa novamente a si mesmo para tentar baixar o arquivo, é aberta uma nova seção do shell, o que consome um pouco de memória. Um script que entrasse em loop poderia consumir uma quantidade muito grande de memória, deixando o sistema lento. Para evitar isso, incluí um "sleep 120", que faz o script dar uma pausa de 120 segundos entre cada tentativa.

Os scripts são muito úteis para automatizar tarefas, que demorariam muito para serem realizas automaticamente. Imagine que você tem uma coleção de arquivos MP3, todos encodados com 256k de bitrate. O problema é que você comprou um MP3Player xing-ling, que só é capaz de reproduzir (com qualidade) arquivos com bitrate de no máximo 160k. Você decide então reencodar todas as músicas para 128k, para ouvi-las no MP3player.

Existem vários programas gráficos que permitem fazer a conversão, entre eles o Grip. Mas, esse é o tipo de coisa que é mais rápido de fazer via linha de comando, usando o lame, como em:

$ lame -b 128 musica.mp3 128k-musica.mp3

Aqui é gerado o arquivo "128k-musica.mp3" (na mesma pasta), encodado com 128k de bitrate, sem modificar o arquivo original.

Para fazer o mesmo com todos os arquivos no diretório, você poderia usar o comando "for", que permite realizar a mesma operação em vários arquivos de uma vez. Ele é muito usado para renomear ou converter arquivos em massa, baseado em determinados critérios. No nosso caso ele poderia ser usado da seguinte forma:

for arquivo in *.{mp3,MP3}

do lame -b 128 "$arquivo" "128k-$arquivo"

done

Aqui, a regra se aplica a todos os arquivos dentro do diretório atual que tiverem extensão ".mp3" ou ".MP3". Para cada um dos arquivos é executado o comando 'lame -b 128 "$arquivo" "128k-$arquivo"', onde o "$arquivo" é substituído por cada um dos arquivos dentro do diretório.

Estes dois exemplos são scripts simples, que simplesmente executam alguns comandos, sem oferecer nenhum tipo de interatividade. Se você quisesse que o primeiro script baixasse outro arquivo, teria que editá-lo manualmente.

0 comentários:

Postar um comentário