GDH Press: Blog » Escrevendo scripts: uma introdução
 
RSS

Escrevendo scripts: uma introdução

Publicado em 08/03/2009 – 16:50
por Carlos Morimoto

Imagine que, em um futuro distante, o Google decida transformar o Android em um sistema para robôs pessoais, complementando os smartphones. Seu robô com o Android poderia ser então instruído a executar qualquer tipo de tarefa, desde que você conseguisse explicar para ele o que precisa fazer na forma de instruções simples. Uma ida até a geladeira para pegar uma lata de refrigerante poderia ser explicada dessa forma:

Ir até a cozinha.
Abrir a geladeira.
Olhar prateleira da direita.
Se encontrar uma lata de coca-cola, trazer para mim.
Senão, trazer a lata de guaraná.
Se não encontrar lata alguma, fechar a geladeira e voltar.

Este mesmo princípio, de dividir a tarefa a ser feita em instruções simples é comum a todas as linguagens de programação. Elas podem variar em complexidade, mas a idéia central é sempre a mesma: explicar ao sistema o que fazer, em uma linguagem que ele seja capaz de entender.

No caso do shell-script, você precisa apenas pensar em uma maneira de "explicar" o que você quer que seja feito através de comandos de terminal. Conforme você vai adquirindo mais familiaridade com o sistema, este acaba se tornando um processo natural, já que qualquer conjunto de comandos para executar uma determinada tarefa pode ser transformado em um script rapidamente. Vamos então a alguns exemplos básicos para quebrar o gelo.

O tipo mais simples de script consiste em um bloco de comandos, que automatiza alguma tarefa repetitiva. Estes scripts "burros" são uma excelente forma de simplificar o uso do sistema, evitando que você precise memorizar sequências de comandos. Um bom exemplo é este mini-script que uso para conectar um mouse bluetooth:

#!/bin/sh
/etc/init.d/bluetooth restart
hciconfig hci0 up
hidd --connect 00:07:61:62:cb:bb

Estes três comandos permitem ativar o mouse em qualquer distribuição, de forma que preciso apenas executar o script e colocá-lo para ser inicializado automaticamente depois de instalar, sem precisar me preocupar com as peculiaridades de cada uma. Simplesmente criei um arquivo de texto chamado "btmouse.sh" dentro da minha partição de dados, marquei a permissão de execução para ele e passei a executá-lo para ativar o mouse:

# ./btmouse.sh

O problema com esses scripts simples é que eles servem para propósitos bem específicos, já que os passos executados são sempre os mesmos. Este script para ativar o mouse bluetooth, por exemplo, funciona apenas com o meu mouse, já que o endereço de conexão está gravado dentro do próprio script.

Scripts mais complexos começam quase sempre com alguma pergunta. Um gerenciador de downloads precisa saber qual é a URL do arquivo a baixar, um discador precisa saber qual modem será utilizado e qual o número de acesso do provedor, um instalador de programas precisa saber qual programa deve ser instalado e assim por diante.

Dependendo da situação, as perguntas podem ser feitas diretamente, no estilo "digite a URL do arquivo a baixar", ou através de um menu de seleção, onde você lista as funções oferecidas pelo script e o usuário clica na opção desejada.

Para fazer uma pergunta direta (que é o formato mais simples), usamos o comando "read", que lê uma resposta e a armazena em uma variável. Quase sempre ele é usado em conjunto com o "echo", que permite escrever texto na tela, fazendo a pergunta, como em:

echo "Digite a URL do arquivo a baixar"
read arquivo
wget -c $arquivo

Com estes três comandos, criamos um gerenciador de downloads primitivo, que pede a URL do arquivo (que você poderia colar no terminal, usando o botão central do mouse) e faz o download usando o wget, que é um gerenciador de downloads em modo texto, muito usado em scripts.

A URL digitada é armazenada na variável "arquivo", que pode ser usada ao longo do script. Ao usá-la, é necessário incluir um "$", que faz com que o shell entenda que se trata da variável "arquivo" e não de um trecho de texto qualquer. Quando o script fosse executado, o "wget -c $arquivo" seria transformado em algo como "wget -c http://gdhpress.com.br/arquivo.zip", iniciando o download.

O "-c" é uma opção para o wget, que faz com que ele continue o download caso interrompido. Se você pressionasse "Ctrl+C" durante o download para encerrar o script e o executasse novamente, fornecendo a mesma URL, ele continuaria o download de onde parou. Você poderia incrementar o script, incluindo mais perguntas, e usando mais opções do wget.

A primeira parada nesse caso seria o "man wget", onde você poderia garimpar as opções suportadas pelo comando e selecionar algumas que poderiam ser úteis dentro do script. Um bom exemplo é a opção "--limit-rate=" que permite limitar a taxa de download. Você poderia incluir a opção no script adicionando mais uma pergunta, como em:

echo "Digite a URL do arquivo a baixar"
read arquivo
echo "Digite a taxa máxima de download, em kbytes. ex: 48"
read taxa
wget -c --limit-rate=$taxa $arquivo

Este formato simples funciona bem para scripts simples, destinados a simplesmente automatizarem alguma tarefa específica, fazendo algumas perguntas simples. Para scripts mais completos, precisamos começar a usar as operações lógicas, que permitem que o script tome decisões. O formato mais básico é o "se, então, senão", que no shell script é representado pelos operadores "if", "then" e "else".

Imagine que você está fazendo um script conversor de vídeos, que é capaz de gerar vídeos em quatro diferentes formatos. O script começa perguntando qual formato usar e, de acordo com a resposta, executa os comandos apropriados para fazer a conversão.

Para simplificar, vamos fazer com que o script simplesmente converta todos os arquivos dentro do diretório atual, em vez de perguntar quais arquivos converter, ou de exibir uma caixa de seleção.

A parte da pergunta, poderia ser feita com o echo, como no exemplo anterior. Como agora são várias linhas de texto, usei aspas simples ( ' ) em vez de aspas duplas. As aspas simples permitem que você inclua quebras de linha e caracteres especiais dentro do texto, fazendo com que o shell simplesmente escreva tudo literalmente:

echo 'Escolha o formato de saída:
1) MPEG4, 320x240 (vídeos no formato 4:3)
2) MPEG4, 320x176 (vídeos em formato wide)
3) Real Player, 320x240 (vídeos no formato 4:3)
4) Real Player, 320x176 (vídeos em formato wide)
(Responda 1, 2, 3, 4 ou 5, ou qualquer outra tecla para sair)'
read resposta

No final deste trecho, teríamos a variável "resposta", que armazenaria um número de 1 a 4. Precisamos agora fazer com que o script decida o que fazer de acordo com a resposta. O jeito mais simples de fazer isso seria simplesmente colocar um bloco com 4 "if", um para cada possibilidade, como em:

if [ "$resposta" = "1" ]; then
[comandos ...]
fi
if [ "$resposta" = "2" ]; then
[comandos ...]
fi
if [ "$resposta" = "3" ]; then
[comandos ...]
fi
if [ "$resposta" = "4" ]; then
[comandos ...]
fi

Uma forma mais elegante (e mais à prova de falhas), seria usar o "elif" (senão se) e o else, como em:

if [ "$resposta" = "1" ]; then
[comandos ...]
elif [ "$resposta" = "2" ]; then
[comandos ...]
elif [ "$resposta" = "3" ]; then
[comandos ...]
elif [ "$resposta" = "4" ]; then
[comandos ...]
else
echo "Você digitou uma opção inválida."
fi

Como pode ver, ao usar o elif você não precisa mais incluir um "fi" para cada possibilidade. Outra vantagem é que você pode agora incluir um "else" no final, que faz com que o script responda com uma mensagem de erro ao receber alguma resposta que não estava esperando. Dentro de cada uma das condições, você incluiria um bloco de comandos destinado a gerar os arquivos convertidos com os parâmetros correspondentes, como em:

for i in *; do
mencoder -oac mp3lame -lameopts cbr:br=128 -ovc lavc -lavcopts \
vcodec=mpeg4:vbitrate=512 -ofps 16 -vf scale=320:176 -o "c-$i" "$i"
done

Este comando gigante na segunda e terceira linha é um comando de conversão de mencoder, que gera um arquivo de vídeo otimizado para ser assistido em smartphones; o tipo de comando que é longo demais para ser escrito manualmente, mas que pode perfeitamente ser usado através de um script. Veja que a variável "i" é usada no final da terceira linha, para indicar o nome do arquivo.

O "c-$i" "$i" faz com que o script adicione o prefixo "c-" no nome dos arquivos convertidos, permitindo que eles sejam incluídos na pasta sem apagar os arquivos originais.

Este comando é colocado dentro de outra condicional, agora usando o "for" (enquanto), que permite que o script execute um conjunto de comandos repetidamente.

No exemplo, ele é usado para fazer com que o comando de conversão do mencoder seja executado uma vez para cada arquivo dentro da pasta. Ao ser executado, a variável "i" (poderia ser qualquer outro nome) recebe o nome do primeiro arquivo, o que faz com que ele seja convertido pelo comando do mencoder.

Ao chegar no "done", o interpretador volta à linha inicial e a variável "i" recebe agora o nome do segundo arquivo. O processo é então repetido para cada um dos arquivos da pasta. O "for i in *; do" poderia ser traduzido como "para cada arquivo dentro da pasta atual, execute".

Você pode baixar o script pronto, incluindo os comandos de conversão no:
http://www.gdhpress.com.br/blog/converter-video/

Outro exemplo de uso para o "for" seria baixar uma lista de arquivos ISO especificada em um arquivo de texto. Imagine que você goste de testar novas distribuições e, de vez em quando, queira deixar o PC ligado durante a madrugada colocando os downloads em dia. Você poderia facilitar as coisas usando um script como:

#!/bin/sh
echo "Digite a taxa máxima de download, em kbytes. ex: 48"
read taxa

for i in `cat /home/$USER/downloads.txt`; do
wget -c --limit-rate=$taxa $i
done

Para usá-lo, você precisaria apenas criar um arquivo "downloads.txt" dentro do seu diretório home e colar os links de download dos ISOs das distribuições, um por linha, como em:

http://ftp.heanet.ie/pub/linuxmint.com/stable/6/LinuxMint-6.iso
ftp://ftp.nluug.nl/pub/os/Linux/distr/dreamlinux/stable/DL3.5_20092802.iso

http://sidux.c3sl.ufpr.br/release/sidux-2009-01-ouranos-kde-lite-i386.iso

Ao executar o script, ele começaria perguntando a taxa máxima de download (com a resposta sendo armazenada na variável "taxa"), leria o arquivo e baixaria os arquivos listados para o diretório atual. A linha do wget inclui agora duas variáveis: a taxa de download e o arquivo a baixar. Por causa do "for", o comando é repetido para cada um dos arquivos listados no arquivo, fazendo com que eles sejam baixados um de cada vez.

Embora simples, este script introduz algumas idéias novas. A primeira é o uso das crases ("), que permitem usar o resultado de um comando. Graças a elas, podemos usar o "cat" para ler o arquivo de texto e assim fazer com que o script carregue as URLs dentro da variável "i", uma de cada vez.

Outra novidade é o uso do "/home/$USER", uma variável de sistema que contém sempre o diretório home do usuário que executou o script. Isso faz com que o script procure pelo arquivo "downloads.txt" dentro do seu diretório home, e não em uma localização específica.

Uma prática um pouco mais avançada é o uso de funções. Elas permitem que você crie blocos de código destinados a executarem tarefas específicas que podem ser usados ao longo do script. Em outras palavras, eles são pequenos scripts dentro do script.

A grande vantagem de criar funções (em vez de simplesmente repetir os comandos quando precisar) é que, ao atualizar o script, você precisa alterar apenas um bloco de comandos, em vez de precisar procurar e alterar os comandos manuais em vários pontos do script. Por permitirem reaproveitar o código, as funções também permitem que o script seja mais organizado e fique com menos linhas, o que facilita muito a manutenção em scripts complexos.

Imagine, por exemplo, que você precisa repetir uma mesma mensagem de erro várias vezes ao longo do script. Você poderia usar uma função como esta:

msgerro()
{
echo "Algo deu errado durante a conexão. O erro foi:"
echo $msg
}

Veja que a função começa com uma declaração ("erro()"), onde você especifica um nome e adiciona o "()", que faz com que o sistema entenda que se trata de uma função e não de um outro bloco de comandos qualquer. Os comandos são em seguida colocados dentro de um par de chaves ( { …. } ), que indicam o início e o final. A partir daí, você pode executar os comandos dentro do script chamando o nome da função (msgerro), como se ele fosse um comando qualquer.

Um exemplo é o script para conectar usando modems e smartphones 3G que escrevi em 2008, que está disponível no: http://www.gdhpress.com.br/blog/script-vivo-zap/

Ele prevê o uso de diversos tipos diferentes de conexão e inclui várias funções de checagem e solução de problemas (que são usadas repetidamente ao longo do script) e por isso é bastante longo e complexo, com quase 900 linhas. Se tiver curiosidade em acessar o tópico e olhar o código, vai ver que crio diversas funções no início do script (a "bpairing()" contém os comandos para fazer o pareamento com smartphones e conectar via Bluetooth, enquanto a "checaporta()" verifica em que porta o modem ou smartphone está conectado, por exemplo) que são usadas ao longo do script.

Outro necessidade comum é salvar parâmetros e configurações, evitando que o script precise perguntar novamente cada vez que for executado. Uma forma simples de fazer isso é fazer com que o script salve as variáveis com as configurações usadas em arquivo de texto (é geralmente usado um arquivo oculto dentro do diretório home), como em:

echo "tel=\"$tel\"" > /home/$USER/.myscript
echo "porta=\"$porta\"" >> /home/$USER/.myscript

Esses comandos criariam o arquivo ".myscript" dentro do diretório home do usuário que executou o script. Graças ao uso do ponto, ele se torna um arquivo oculto, assim como os demais arquivos de configuração do sistema.

O arquivo de configuração pode ser carregado dentro

do script com um: . /home/$USER/.3gconfig" (ou seja, ponto, espaço e a localização do arquivo), o que faz com que o interpretador processe os comandos dentro do arquivo como se fossem parte do script principal.

Um exemplo de uso seria uma versão aperfeiçoada do script para ativar mouses Bluetooth que mostrei a pouco, que perguntasse o endereço do mouse da primeira vez que fosse executado e passasse a usar a configuração salva daí em diante:

#!/bin/sh
if [ -e "/home/$USER/.btmouse" ]; then
echo "Carregando configuração salva no arquivo /home/$USER/.btmouse."
. /home/$USER/.btmouse
else
# Se o arquivo não existir, pergunta o endereço e salva a configuração.
echo "Digite o endereço do mouse que será usado (ex: 00:07:61:62:cb:bb):"
read addr
echo "addr=\"$addr\""
fi

# Mensagem explicativa:
echo "Conectando a $addr"
echo "Delete o arquivo /home/$USER/.btmouse para trocar o endereço."

# O script propriamente dito:
/etc/init.d/bluetooth restart
hciconfig hci0 up
hidd --connect $addr

Embora estes exemplos utilizem apenas perguntas simples, em texto, os shell-scripts podem também exibir janelas, avisos, perguntas e menus de seleção gráficos de maneira muito simples, utilizando o Xdialog, Kdialog ou o Zenity, que permitem mostrar janelas gráficas de forma surpreendentemente simples.

Para mostrar uma mensagem de texto, por exemplo, você usaria o "zenity --info --text" seguido da mensagem a mostrar, como em:

zenity --info --text "Conexão ativa."

Para abrir uma janela de seleção de arquivo, você usaria o "zenity --file-selection", o que exibe uma janela similar à do gerenciador de arquivos. Para que a localização do arquivo escolhido a uma variável, você usaria:

arquivo=`zenity --file-selection --title "Escolha o arquivo"`

Estes dois comandos simples poderiam ser usados para criar um aplicativo rudimentar de gravação de CDs, veja só:

#!/bin/sh
zenity --info --text "Coloque uma mídia virgem no drive"
iso=`zenity --file-selection --title "Escolha o arquivo ISO para gravar:"`
wodim dev=/dev/scd0 speed=16 -dao -eject -v $iso

Quando executado, o script mostraria as duas janelas e gravaria o arquivo ISO selecionado usado o wodim, que um aplicativo de gravação de CDs via linha de comando, usado por diversos outros aplicativos. Quando você queima um CD usando o Brasero ou o K3B, é o wodim quem faz o trabalho pesado, assim como no caso do nosso script:

shell_html_m55db5c2c

shell_html_m13d18a3a

Graças à opção "-eject" adicionada à linha de gravação, o script ejeta a mídia depois de concluída a gravação, o que torna desnecessário incluir mais uma janela avisando que a gravação foi concluída. O wodim suporta diversas outras opções de gravação (que você pode consultar no "man wodim"), que poderiam ser usadas para aperfeiçoar o script. Você precisaria apenas incluir algumas perguntas adicionais, salvar as respostas em variáveis e incluí-las na linha de gravação.

» Leia também:

Uma introdução à linha de comando

Comandos do prompt

Aplicativos em modo texto

» Mais posts

  1. 13 respostas para “Escrevendo scripts: uma introdução”

  2. Marcelo em 8 mar, 2009

    Legal,

    Talvez em um futuro não muito distante cada bloco de scripts esteja na "nuvem" via wireless e um poderoso sistema operacional dotado de Inteligência Artificial iria gerenciar tudo isso de acordo com a necessidade do usuário. Os comandos seriam por voz, talvez usando a tecnologia voice xml.

  3. ciro em 8 mar, 2009

    O que eu posso dizer além de obrigado ? :D
    Posso dizer ainda tenho algumas duvidas… :P
    Primeiro
    No trecho onde é usado uma sequencia de elif, poderia ser usado case?
    Segundo
    O "FOR" (enquanto) não seria "FOR"( para cada) ?
    Terceira( essa é coisa de gente ranzinza)
    Aspas simples não seriam apóstrofos ' e Aspas duplas seriam as aspas " apenas ?.

    Valeu pelo artigo, depois dele , vou ler um script com outros olhos, aliás na pasta /usr/local/bin tem um playgroud de scrpits pra estudar, com bom senso é claro, já pra lá ;)

  4. Raul_Kl em 8 mar, 2009

    Parabéns pelo artigo Morimoto!

    Acho que este é um bom tema para mais um livro, um dos poucos que ainda faltam.

    Tenho o livro "Shell Script Profissional" do Aurélio Marinho Jargas, muito bom livro, mas os exemplos usados neste, não tem muita utilidade para fins práticos. Um livro ensinando Shell Script usando exemplos aplicáveis no dia-a-dia, como os deste artigo, seria muito melhor, com certeza.

  5. dalencar em 9 mar, 2009

    Artigo muito bom! As dicas são muito úteis!

  6. itamarnet em 9 mar, 2009

    Esse trecho porderia ser substituido por algo assim, que acredito ser mais adequado:
    Trocar isso:
    for i in `cat /home/$USER/downloads.txt`; do
    wget -c --limit-rate=$taxa $i
    done

    Por isso:
    wget -c --limit-rate=$taxa -i /home/$USER/downloads.txt

  7. Carlos Morimoto em 9 mar, 2009

    Os exemplos dessa dica têm o único objetivo de serem didáticos e fáceis de entender. Naturalmente existem maneiras mais concisas, práticas e/ou elegantes de escrever cada um deles.

  8. Artus em 9 mar, 2009

    As dicas do Morimoto, otimas como sempre.
    Eu iniciei nos scripts por meio de uma outra dica introdutoria dele.
    Um livro sobre o assunto cairia bem, o material disponivel no site serviria como introdução, e poderia se aprofundar mais no livro, mostrndo as possibilidades de diversos comandos Unix, como until, while, sed, tr, case, for, etc…
    Ou melhor poderia ser sobre linguagens interpretadas, adicionando algo sobre Python, e outras, mais focando em shell.
    A escrita do Morimoto é tão clara, que poderia ser usada no Ensino médio,( isto sim seria inclusão digital :D). Ensina-se fisica e quimica, não vejo dificuldade nenhuma e se ensinar uma linguagem de programação, ainda mais uma tão clara e simples. Seria como um novo idioma :)

  9. chino ventura em 9 mar, 2009

    Será que poderíamos esperar o mesmo texto numa próxima versão do "Ferramentas Técnicas: Guia prático"?… valeu!

  10. K8C40_r em 9 mar, 2009

    será?
    ehehe
    quem sabe neh
    parbéns…

  11. Rox em 10 mar, 2009

    Muito Show :D
    gostei do Tutorial
    bem Explicativo..
    eu queria saber se Existe algum Livro Bom
    de Scrip Para Windows..
    Se tiver de umas dica ae
    Vlw
    ;)

  12. Jesxmaa em 21 mar, 2009

    A idéia do colega Artus de se ensinar shell script no ensino médio é realmente muito boa. poderia ser incluída nas aulas de informática que os estudantes do primeiro ano tem. seria realmente enriquecedor para eles nessa era digital. eu queria muito ter podido estudar algo assim na minha época ^^
    abraço, excelente tutorial como sempre mestre!

  13. plotnet em 29 jun, 2009

    Acho a ideia bacana, a de o Morimoto publicar um livro sobre shell-script, do básico ao avançado. Do estilo dos demais, como os Guias Práticos e o Definitivo.

  14. annakamilla em 1 jul, 2009

    matou minha duvida sobre elseif. mais tarde leio mais atentamente, salvei o texto no meu pc.


Comente: