ct

    Guia: Programando em Shell Script


     Carlos E. Morimoto
     22/03/2005



    Quase tudo no Linux pode ser feito via linha de comando. É possível baixar e instalar programas automaticamente, alterar qualquer tipo de configuração do sistema, carregar programas ou executar operações diversas durante o boot, entre muitas outras possibilidades. Dentro do KDE é possível até mesmo controlar programas gráficos, minimizando uma janela, abrindo um novo e-mail, já com o corpo da mensagem preenchido no kmail, exibir uma mensagem de aviso, criar ou eliminar ícones no desktop, e assim por diante.

    Um script é um arquivo de texto, com uma seqüência de comandos que são executados linha a linha. Dentro de um script, você pode utilizar qualquer comando de terminal (incluindo programas gráficos e parâmetros para eles) e também funções lógicas suportadas pelo shell, que incluem operações de tomada de decisão, comparação, etc. Você pode até mesmo acessar bancos de dados ou configurar outras máquinas remotamente.

    Assim como o perl, python e o lua, o shell script é uma linguagem interpretada, onde o próprio script, escrito num editor comum é o executável. Você pode alterá-lo rapidamente e executá-lo logo em seguida para testar a mudança. Nas linguagens tradicionais, o código fonte precisa ser recompilado a cada modificação, o que toma tempo.

    A princípio, o shell script lembra um pouco os arquivos .bat do DOS, que também eram arquivos de texto com comandos dentro; da mesma forma que um ser humano e uma ameba conservam muitas coisas em comum, como o fato de possuírem DNA, se reproduzirem e sintetizarem proteínas. Mas, assim como um humano é muito mais inteligente e evoluído que uma ameba, um shell script pode ser incomparavelmente mais poderoso e elaborado que um simples .bat do DOS.

    É possível escrever programas complexos em shell script, substituindo aplicativos que demorariam muito mais tempo para ser escritos em uma linguagem mais sofisticada. Seus scripts podem tanto seguir a velha guarda, com interfaces simples de modo texto (ou mesmo não ter interface alguma e serem controlados através de parâmetros), de forma a desempenhar tarefas simples, quanto possuir uma interface gráfica elaborada, escrita usando o kommander e funções do kdialog.

    Isso vai de encontro à idéia que muitas pessoas, incluindo até mesmo usuários Linux tarimbados, possuem. O uso de scripts pode ir muito além de simples scripts de configuração. Você pode desenvolver programas bastante complexos se usar as ferramentas certas.

    Um exemplo de trabalho desenvolvido em shell script é o painel de controle do Kurumin, que utiliza um conjunto de painéis gráficos, criados usando o Kommander, que ativam um emaranhado de scripts para desempenhar as mais diversas tarefas.

    O programa de instalação do Kurumin também é escrito em shell script, assim como a maior parte dos programas encarregados de configurar o sistema durante o boot, os painéis para instalar novos programas, configurar servidores e tudo mais.

    O principal motivo para uso de scripts em shell ao invés de programas escritos em C ou C++, por exemplo, é a rapidez de desenvolvimento, combinada com a facilidade de editar os scripts existentes para corrigir problemas ou adicionar novos recursos. Você vai encontrar uma grande quantidade de scripts em qualquer distribuição Linux, incluindo os próprios scripts de inicialização.


    O básico


    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.

cb
HOME