terça-feira, 28 de fevereiro de 2012

Como escrever shell scripts (parte 2)

No artigo anterior, conhecemos a estrutura de um arquivo shell script e aprendemos como utilizar variáveis. Antes de irmos mais a fundo, vamos aprender a como combinar os comandos do shell uns com os outros, gerando resultados poderosos.



É claro que não podemos, aqui, explicar detalhadamente todos os comandos padrão do Linux - nem essa é a nossa intenção! - mas vamos apenas dar a ideia geral para que você possa utilizá-la em seus scripts posteriormente.



Comandos Sequenciais


Como você deve saber,é possível executar vários comandos colocando um deles em cada linha - ou digitando-o e apertando-se enter, caso esteja-se no prompt. No entanto, também é possível executar-se vários comandos em uma única instrução. Fazer isso nos fornece vantagens tanto estéticas quanto técnicas. Existem três maneiras de se executar mais de um comando na mesma instrução.


Para executar vários comandos, independente de seu resultado, simplesmente separe-os com um ponto-e-vírgula:


comando1 ; comando2 ; ... ; comandon


Assim, o interpretador, no caso o shell Bash, executará o comando1. Quando comando1 for concluido,ele executará o comando2 e assim sucessivamente até o fim da lista.


Embora simples, essa sintaxe apresenta uma desvantagem: não há controle sobre o resultado da execução de um comando, o que pode gerar resultados inesperados em caso de erro caso uma instrução dependa da outra.


Exemplificando: digamos que você queira baixar a imagem do CD de uma distro e gravá-la em um disco com o comando cdrecord automaticamente. Você poderia fazer isso com uma sequência de comandos similar à abaixo (obs: o código abaixo não foi testado xd):


wget -c http://opensuse.c3sl.ufpr.br/distribution/12.1/iso/openSUSE-12.1-KDE-LiveCD-i686.iso ; cdrecord dev=0,0,0 -v speed=20 -fs=16 -eject -data openSUSE-12.1-KDE-LiveCD-i686.iso


Assim, é só deixar um CD-R em branco no drive, rodar o comando e a mágica acontece. Mas e se a sua conexão cair no meio do download? Neste caso, você acabaria tendo uma iso incompleta que seria gravada no CD. Como resultado, você perderia uma mídia novinha em folha.


Seria interessante, então, que segundo comando apenas fosse executado se a iso tivesse sido baixada corretamente. E há como fazer isso: basta substituir o ; por && :


wget -c http://opensuse.c3sl.ufpr.br/distribution/12.1/iso/openSUSE-12.1-KDE-LiveCD-i686.iso && cdrecord dev=0,0,0 -v speed=20 -fs=16 -eject -data openSUSE-12.1-KDE-LiveCD-i686.iso


Como o sistema sabe que um comando foi executado com sucesso? Por padrão, quando um comando é executado com êxito, ele retorna o valor 0 ao interpretador; se algo dá errado, ele retorna qualquer outro número inteiro. O significado dos outros números depende de cada programa, mas o certo é que, se deu certo, o Bash recebe o valor 0 e, então, executa o segundo comando. Se a conexão cair ou o arquivo for removido do servidor ou, se por quaisquer outros motivos o download for interrompido, o wget vai retornar ao Bash um valor diferente de 0 e a execução vai parar, com a imagem incompleta não sendo gravada no disco.


É possível, também, executar comandos em caso de falha com o operador ||. No caso abaixo, ao invés de gravar, exibimos uma mensagem em caso de erro:


wget -c http://opensuse.c3sl.ufpr.br/distribution/12.1/iso/openSUSE-12.1-KDE-LiveCD-i686.iso || echo "Deu erro!"



Redirecionando o resultado de um comando para um arquivo


Casa comando ou processo em sistemas Unix ou Unix-like abre três descritores padrão para controlar a entrada e a saída dos dados. O descritor de entrada, stdin, geralmente é o teclado e serve para introduzirmos dados no programa. Ele pode ser acessado através do dispositivo /dev/stdin; o descritor de saída, stdout, acessível através de /dev/stdout, exibe os resultados do comando executado e o descritor de erros, stderr, representado por /dev/stdrr, exibe as mensagens de erro durante a execução do comando.Os valores numéricos para stdin, para stdout e para stderr são, respectivamente, 0, 1 e 2. Por padrão, tanto stdout quanto stderr utilizam a tela do nosso monitor para exibir seus resultados. No entanto, se quisermos, podemos redirecioná-los para arquivos ou para outros dispositivos, tornando o resultado mais "limpo" e de fácil entendimento.


Para redirecionarmos a saída padrão de um comando para um arquivo, utilizamos o operador >. No exemplo abaixo, redirecionamos a saída do comando ls -laR / para um arquivo texto chamado meusarq:


$ ls -laR / > meusarq


O resultado desse comando será um arquivo texto com a lista de todos os arquivos da máquina.


Como você deve saber, no Linux os dispositivos também são tratados como arquivos. Assim, o redirecionador > também pode ser utilizado para redirecionar a saída de comandos para estes. Um truque bem legal de se fazer é digitar-se no terminal o comando:


$ cat /dev/random > /dev/dsp


Se você digitar essa instrução, vai ouvir um barulho interessante. Por quê? O dispositivo /dev/random gera números pseudo aleatórios e o /dev/dsp representa seu dispositivo de som. Assim, o que você ouve são sons aleatórios enviados para sua placa de som!


O redirecionador > sobrescreve o arquivo de destino que você especificar. Para adicionar novo conteúdo a esse arquivo, utiliza-se o operador >>.


Por exemplo: se você tiver um arquivo chamado texto1 e outro chamado texto2, se você fizer algo como


$ cat texto2 > texto1


então o conteúdo de texto1 será substituído pelo de texto2 (o comando cat exibe o conteúdo de um arquivo na tela). Agora, se ao invés disso você digitar


$ cat texto2 >> texto1


o resultado será um arquivo com o conteúdo de texto1 imediatamente seguido pelo conteúdo de texto2.


Se, ao invés da saída padrão, você quiser redirecionar a saída de erro, use 2>. Ao digitar uma instrução como


$ comando 2> erros.txt


apenas o resultado da execução de comando vai aparecer na tela e quaisquer mensagens de erro serão gravadas em erros.txt. Se você quiser redirecionar tanto stdout quanto stdin para um arquivo, utilize &>.


Conforme você deve ter notado, ao utilizarmos >, como no exemplo do ls, o resultado não é exibido na tela. Veremos como resolver isso na próxima seção.



Canalização


Além de executar comandos em sequência e permitir o redirecionamento de sua saída, o shell Linux também permite a canalização dos processos através do operador | (não confundir com ||, que é utilizado para executar um comando em caso de falha do anterior). Na prática, o que | faz é simplesmente tomar o resultado do primeiro comando como parâmetro do segundo.


Por exemplo: digamos que o navegador Firefox tenha travado e você queira matar seu processo através do comando kill -9 (é claro que você poderia fazer isso por um killall firefox-bin, mas você predere a forma mais difícil). Então, tudo que você precisa de fazer é digitar kill -9 PID, onde PID é o número do processo do navegador. Mas como descobrir esse número? É muito simples:


$ ps aux | grep firefox-bin | grep-v grep


Vamos por partes, como diria Jack. Perceba que, aqui, nós temos três comandos na mesma linha. O primeiro deles, ps aux, lista todos os processos que estão sendo executados naquele momento. Através do operador |, redirecionamos sua saída para o comando grep firefox-bin, que vai pegar todas as linhas do ps e retornar apenas aquelas que possuam a expressão firefox-bin. No entanto, esse comando vai retornar dois resultados: um será o próprio navegador e o outro será o comando grep! Para arrumar, canalizamos o resultado do primeiro grep para o comando grep -v grep, que significa: "exiba apenas as linhas que não possuam a palavra grep". O segundo número da linha retornada será o pid do processo. Posteriormente, você poderia melhorar esse script com sed ou awk para matar o processo nessa mesma linha.


Lembra de que falei que, ao redirecionar um comando para um arquivo, deixamos de ver seu resultado na tela? O comando tee resolve essa pendência: ele permite redirecionar a saída de um comando tanto para um arquivo quanto para a tela ao mesmo tempo. Sua sintaxe é:


$ comando | tee arquivo.txt


Há, ainda, o útil comando xargs, que passa ao comando especificado o que foi recebido da entrada padrão como argumento:


$ cat arquivo | xargs echo


Agora que você viu como podemos combinar e redirecionar comandos, estamos prontos para seguir em frente. Na próxima semana, vamos ensinar nossos programas a tomarem decisões. Até lá!

Nenhum comentário:

Postar um comentário