Uma introdução ao shell-script

Imagine que, em um futuro distante, o Google decida transformar o Android em um sistema para robôs pessoais. 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 a 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 ideia 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
# Checagem para ter certeza que o suporte a bluetooth está ativado:
hciconfig hci0 down
/etc/init.d/bluetooth restart
hciconfig hci0 up
# Ativa o mouse:
hidd –connect 00:07:61:62:cb:bb

Estes quatro comandos permitem ativar o mouse em qualquer distribuição, de forma que preciso apenas executar o script e colocá-lo para ser inicializado automaticamente, sem precisar me preocupar com as peculiaridades de cada uma.

Para usá-lo, é necessário apenas criar um arquivo de texto chamado “btmouse.sh” (ou qualquer outro nome que escolher), colocá-lo dentro do seu home, ou da sua partição de dados (para que ele não seja perdido ao reinstalar o sistema) e marcar a permissão de execução (“chmod +x btmouse.sh”).

A partir daí, você pode passar a executar o script quando precisar ativar o mouse:

# /mnt/sda6/btmouse.sh

… ou adicionar o comando no final do arquivo “/etc/rc.d/rc.local” (ou outro arquivo de inicialização) para que ele passe a ser executado automaticamente.

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 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 rudimentares, destinados a simplesmente automatizarem alguma tarefa específica, a partir de 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 arquivos 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, 320×240 (vídeos no formato 4:3)
2) MPEG4, 320×176 (vídeos em formato wide)
3) Real Player, 320×240 (vídeos no formato 4:3)
4) Real Player, 320×176 (vídeos em formato wide)
(Responda 1, 2, 3 ou 4, 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 comandos “if” (se), um para cada possibilidade. Cada “if” é sempre acompanhado por um “then” (então) e um “fi” (fim do se), 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” (que poderia ser traduzido para “senão se”) e o “else” (senão), 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. O script termina aqui."
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:

if [ "$resposta" = "1" ]; then
    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

elif [ "$resposta" = "2" ]; then
    [o resto do script...]

Esse comando gigante que se esparrama pela terceira e a quarta linha é um comando de conversão do mencoder (um pequeno utilitário de conversão de arquivos de mídia em modo texto, que é bastante flexível), que gera um arquivo de vídeo otimizado para ser assistido em smartphones. Esse é 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 quarta 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.

O comando do mencoder é 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.

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, até o último (a lista dos arquivos a converter é gerada pelo interpretador no início do script, por isso não existe o risco do conversor ficar em loop). 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:
https://www.hardware.com.br/dicas/converter-video.html

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, colocando 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 duas variáveis: a taxa de download e o arquivo a baixar. Por causa do “for”, o comando é repetido para cada um dos links 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, as funções 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 fazem com 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 (“msgerro()”), 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 fosse um comando qualquer.

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

Ele prevê o uso de diversos tipos 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), por isso ele é 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.

Outra necessidade comum é salvar parâmetros e configurações, evitando que o script precise perguntar tudo novamente cada vez que for executado. Uma forma simples de implementar 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/.myscript” (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\"" > /home/$USER/.btmouse
fi

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

# O script propriamente dito:
hciconfig hci0 down
/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 fácil.

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”, que exibe uma janela similar à do gerenciador de arquivos, permitindo escolher o arquivo que será usado.

Normalmente, a localização do arquivo seria simplesmente escrita no terminal. Para que ela seja armazenada em uma variável (permitindo que você a use posteriormente ao longo do script), 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:

Graças à opção “-eject” adicionada à linha de gravação, o script ejeta a mídia depois de gravada, 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.

Concluindo essa breve introdução, dois outros comandos fundamentais em se tratando de shell-scripts são o pipe e o grep. Aqui vai uma explicação resumida sobre eles:

pipe: Junto com as setas de redirecionamento (> e >>), o pipe ( | ) é muito usado em scripts e comandos diversos. Ele permite fazer com que a saída de um comando seja enviada para outro ao invés de ser mostrada na tela. Parece uma coisa muito exótica, mas acaba sendo incrivelmente útil, pois permite “combinar” diversos comandos que originalmente não teriam nenhuma relação entre si, de forma que eles façam alguma coisa específica. Um exemplo simples é o “modprobe -l | more”, que vimos no tópico sobre o kernel, onde o pipe é usado para que a enorme lista gerada pelo comando “modprobe -l” seja enviada ao “more”, que se encarrega de criar as quebras de página.

grep: O grep permite filtrar a saída de um determinado comando, de forma que ao invés de um monte de linhas, você veja apenas a informação que está procurando. Ele é frequentemente usado em conjunto com o pipe, sobretudo em scripts.

Um exemplo simples: sua placa de rede não está funcionando e você quer saber se o módulo de kernel “sis900”, que dá suporte a ela, está carregado. Você pode ver os módulos que estão carregados usando o comando “lsmod”, mas a lista é um pouco longa. Você poderia completar o lsmod com o “| grep sis900”, que vai filtrar usando o grep, mostrando na tela apenas as linhas contendo “sis900”. O comando ficaria então “lsmod | grep sis900”.

Se não aparecer nada na tela, você sabe de antemão que o módulo não está ativo. Neste caso, você poderia tentar carregá-lo manualmente usando o comando “modprobe sis900”, como root.

Em um script, o mesmo comando poderia ser usado para detectar se o módulo está carregado ou não e, a partir daí, mostrar uma mensagem na tela, ou carregá-lo automaticamente.

Sobre o Autor

Redes Sociais:

Deixe seu comentário

X