Programação Shell LinuxAqui temos um livro livre e completo sobre ShellOs sedentos do "saber livre" são muito benvindos. |
Comandos Shell Script |
||
Home | Artigos | Agradecimentos | Links Amigos | Comprar o livroChangelogs |
Papo de Botequim - Parte 1Papo de Botequim - Parte 2Papo de Botequim - Parte 3Papo de Botequim - Parte 4Papo de Botequim - Parte 5Papo de Botequim - Parte 6Papo de Botequim - Parte 7Papo de Botequim - Parte 8Papo de Botequim - Parte 9Papo de Botequim - Parte 10Papo de Botequim - Parte 11Tira Gosto |
Papo de Botequim - Parte VIComandos de Loop ou Laço (Continuação)- Fala cara! E aí, já tá sabendo tudo do comando - Claro! Tô empolgadão com essa linguagem, eu fiz da forma que você pediu, isto é sem usar o comando - Êpa! Perai! Você realmente está fissurado na linguagem, mas eu tô sequinho pra tomar um chope. Aê Chico, traz dois por favor. Um sem colarinho! - Como eu ia dizendo olha a forma que eu fiz. É muito fácil...
$ cat contpal.sh
#!/bin/bash
# Script meramente pedagógico cuja
# função é contar a qtd de palavras
# de um arquivo. Supõe-se que as
# palavras estão separadas entre si
# por espaço,
if [ $# -ne 1 ] then echo uso: $0 /caminho/do/arquivo exit 2 fi Cont=0 for Palavra in $(cat $1) do Cont=$((Cont+1)) done echo O arquivo $1 tem $Cont palavras.
Ou seja, o programa começa como sempre verificando se a passagem de parâmetros foi correta, em seguida o comando
Vamos relembrar como é o arquivo $ cat ArqDoDOS.txt
Este arquivo
foi gerado pelo
DOS/Rwin e foi
baixado por um
ftp mal feito.
Agora vamos testar o programa passando este arquivo como parâmetro: $ contpal.sh ArqDoDOS.txt
O arquivo ArqDoDOS.txt tem 14 palavras
- Beleza, funcionou legal! Um Pouco Mais de
|
Expansão Aritmética | |
---|---|
|| |
OU lógico |
Expressão | Resultado |
id++ id-- |
pós-incremento e pós-decremento de variáveis |
++id -–id |
pré-incremento e pré-decremento de variáveis |
** |
exponenciação |
* / % |
multiplicação, divisão, resto da divisão |
+ - |
adição, subtração |
<= >= < > |
comparação |
== != |
igualdade, desigualdade |
&& |
E lógico |
- Mas você pensa que o papo de loop (ou laço) se encerra no comando for
? Ledo engano amigo, vamos a partir de agora ver mais dois.
while
Todos os programadores conhecem este comando, porque ele é comum a todas as linguagens e nelas, o que normalmente ocorre é que um bloco de comandos é executado, enquanto (enquanto em ingles é while) uma determinada condição for verdadeira. Pois bem, isto é o que ocorre nas linguagens caretas! Em programação Shell, o bloco de comandos é executado enquanto um comando for verdadeiro. E é claro, se quiser testar uma condição use o comando while
junto com o comando test
, exatamente como você aprendeu a fazer no if
, lembra?
Então a sintaxe do comando fica assim:
while comando do cmd1 cmd2 ... cmdn done
e desta forma o bloco de comandos formado pelas instruções cmd1
, cmd2
,... e cmdn
é executado enquanto a execução da instrução comando
for bem sucedida.
Suponha a seguinte cena: tem uma tremenda gata me esperando e eu preso no trabalho sem poder sair porque o meu chefe, que é um pé no saco (aliás chefe-chato é uma redundância, né?:), ainda estava na sua sala, que fica bem na minha passagem para a rua.
Ele começou a ficar com as antenas (provavelmente instaladas na cabeça dele pela esposa) ligadas depois da quinta vez que passei pela sua porta e olhei para ver se já havia ido embora. Então voltei para a minha mesa e fiz, no servidor, um script assim:
# Espero que a Xuxa não tenha # copyright de xefe e xato :)
while who | grep xefe do sleep 30 done echo O xato se mandou, não hesite, dê exit e vá a luta
Neste scriptizinho, o comando while
testa o pipeline composto pelo who
e pelo grep
e que será verdadeiro enquanto o grep
localizar a palavra xefe
na saída do who
. Desta forma, o script dormirá por 30 segundos enquanto o chefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o fluxo do script sairá do loop e dará a tão ansiada mensagem de liberdade.
Quando o executei adivinha o que aconteceu?
Isto é a cada 30 segundos seria enviado para a tela a saída do grep
, o que não seria legal já que poluiria a tela do meu micro e a mensagem esperada poderia passar desapercebida. Para evitar isso já sabemos que a saída do pipeline tem que ser redirecionada para /dev/null
.
# Espero que a Xuxa não tenha # copyright de xefe e xato :)
while who | grep xefe > /dev/null do sleep 30 done echo O xato se mandou, não hesite, dê exit e vá a luta
Agora quero montar um script que receba o nome (e eventuais parâmetros) de um programa que será executado em background e que me informe do seu término. Mas, para você entender este exemplo, primeiro tenho de mostar uma nova variável do sistema. Veja estes comandos diretos no prompt:
Isto é, criei um processo em background para dormir por 10 segundos, somente para mostrar que a variável $!
guarda o PID (Process IDentification) do último processo em background, mas repare após a linha do done
, que a variável reteve o valor mesmo após o término deste processo.
Bem sabendo isso já fica mais fácil de monitorar qualquer processo em background. Veja só como:
# Executa e monitora um # processo em background
$1 & # Coloca em backgroud while ps | grep -q $! do sleep 5 done echo Fim do Processo $1
Este script é bastante similar ao anterior, mas tem uns macetes a mais, veja só: ele tem que ser executado em background para não prender o prompt mas o $!
será o do programa passado como parâmetro já que ele foi colocado em background após o monbg.sh
propriamente dito. Repare também a opção -q
(quiet) do grep
, ela serve para tranformá-lo num comando mineiro, isto é, para o grep
"trabalhar em silêncio". O mesmo resultado poderia ser obtido se a linha fosse while ps | grep $! > /dev/null
, como nos exemplos que vimos até agora.
$!
que possui o PID
(Process IDentification) do último processo executado em background.
Vamos melhorar o musinc
, que é o nosso programa para incluir registros no arquivo musicas
, mas antes preciso te ensinar a pegar um dado da tela, e já vou avisando: só vou dar uma pequena dica do comando read (que é quem pega o dado da tela) que seja o suficiente para resolver este nosso problema. Em uma outra rodada de chope vou te ensinar tudo sobre o assunto, inclusive como formatar tela, mas hoje estamos falando sobre loops.
A sintaxe do comando read
que nos interessa por hoje é a seguinte:
Onde prompt de leitura
é o texto que você quer que apareça escrito na tela, e quando o operador teclar o dado, ele irá para a variável var
. Por exemplo:
Bem, uma vez entendido isso, vamos à especificação do nosso problema: faremos um programa que inicialmente lerá o nome do álbum e em seguida fara um loop de leitura, pegando a música e o artista. Este loop termina quando for informada uma música vazia, isto é, ao ser solicitada a digitação da música, o operador dá um simples <ENTER>
. Para facilitar a vida do operador, vamos oferecer como default o mesmo nome do artista da música anterior (já que é normal que o álbum seja todo do mesmo artista) até que ele deseje alterá-lo. Vamos ver como ficou:
Este exemplo, começa com a leitura do título do álbum, que se não for informado, terminará a execução do programa. Em seguida um grep
procura no início (^
) de cada registro de musicas, o título informado seguido do separador (^
) (que está precedido de uma contrabarra (\
) para protegê-lo da interpretação do Shell).
Para ler os nomes dos artistas e as músicas do álbum, foi montado um loop de while
simples, cujo único destaque é o fato de estar armazenando o artista da música anterior na variável $oArt
que só terá o seu conteúdo alterado, quando algum dados for informado para a variável $Art
, isto é, quando não teclou-se um simples <ENTER>
para manter o artista anterior.
O que foi visto até agora sobre o while
foi muito pouco. Este comando é muito utilizado, principalmente para leitura de arquivos, porém nos falta bagagem para prosseguir. Depois que aprendermos a ler, veremos esta instrução mais a fundo.
while
quando seu uso for desnecessário. O Shell tem ferramentas como o sed
e a família grep
que vasculham arquivos de forma otimizada sem ser necessário o uso de comandos de loop para fazê-lo registro a registro (ou até palavra a palavra).
until
O comando until
funciona exatamente igual ao while
, porém ao contrário. Disse tudo mas não disse nada, né? É o seguinte: ambos testam comandos; ambos possuem a mesma sintaxe e ambos atuam em loop, porém enquanto o while
executa o bloco de intruções do loop enquanto um comando for bem sucedido, o until
executa o bloco do loop até que o comando seja bem sucedido. Parece pouca coisa mas a diferença é fundamental.
A sintaxe do comando é praticamente a mesma do while
. Veja:
until comando do cmd1 cmd2 ... cmdn done
E desta forma o bloco de comandos formado pelas instruções cmd1
, cmd2
,... e cmdn
é executado até que a execução da instrução comando
seja bem sucedida.
Como eu te disse, o while
e until
funcionam de forma antagônica e isso é muito fácil de demonstrar: em uma guerra sempre que se inventa uma arma, o inimigo busca uma solução para neutralizá-la. Baseado neste principio belicoso que o meu chefe, desenvolveu, no mesmo servidor que eu executava o logaute.sh
um script para controlar o meu horário de chegada.
Um dia deu um problema da rede, ele me pediu para dar uma olhada no micro dele e me deixou sozinho em sua sala. Imediatamente comecei a bisbilhotar seus arquivos - porque guerra é guerra - e veja só o que descobri:
until who | grep julio do sleep 30 done echo $(date "+ Em %d/%m às %H:%Mh") >> relapso.log
Olha que safado! O cara estava montando um log com os horários que eu chegava, e ainda por cima chamou o arquivo que me monitorava de relapso.log
! O que será que ele quis dizer com isso?
Neste script, o pipeline who | grep julio
, será bem sucedido somente quando julio
for encontrado no comando who
, isto é, quando eu me "logar" no servidor. Até que isso aconteça, o comando sleep
, que forma o bloco de instruções do until
, porá o programa em espera por 30 segundos. Quando este loop encerrar-se, será dada uma mensagem para o relapso.log
(ARGHH!). Supondo que no dia 20/01 eu me loguei às 11:23 horas, a mensagem seria a seguinte:
Em 20/01 às 11:23h
Quando vamos cadastrar músicas, o ideal seria que pudéssemos cadastrar diversos CDs, e na última versão que fizemos do musinc
, isso não ocorre, a cada CD que cadastramos o programa termina. Vejamos como melhorá-lo:
Nesta versão, um loop maior foi adicionado antes da leitura do título, que só terminará quando a variável $Para
deixar de ser vazia. Caso o título do álbum não seja informado, a variável $Para
receberá valor (no caso coloquei 1
mas poderia ter colocado qualquer coisa. O importante é que não seja vazia) para sair deste loop, terminando desta forma o programa. No resto, o script é idêntico à sua versão anterior.
Nem sempre um ciclo de programa, compreendido entre um do
e um done
, sai pela porta da frente. Em algumas oportunidades, temos que colocar um comando que aborte de forma controlada este loop. De maneira inversa, algumas vezes desejamos que o fluxo de execução do programa volte antes de chegar ao done. Para isto, temos respectivamente, os comandos break
(que já vimos rapidamente nos exemplos do comado while
) e continue
, que funcionam da seguinte forma:
O que eu não havia dito anteriormente é que nas suas sintaxes genéricas eles aparecem da seguinte forma:
break [qtd loop]
e
continue [qtd loop]
Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandos irão atuar. Seu valor default é 1.
Duvido que você nunca tenha deletado um arquivo e logo após deu um tabefe na testa se xingando porque não devia tê-lo removido. Pois é, na décima vez que fiz esta besteira, criei um script para simular uma lixeira, isto é, quando mando remover um (ou vários) arquivo(s), o programa "finge" que removeu-o, mas no duro o que fez foi mandá-lo(s) para o diretório /tmp/LoginName_do_usuario. Chamei este programa de erreeme
e no /etc/profile coloquei a seguinte linha:
alias rm=erreeme
O programa era assim:
if [ $# -eq 0 ] # Tem de ter um ou mais arquivos para remover then echo "Erro -> Uso: erreeme arq [arq] ... [arq]" echo " O uso de metacaracteres e’ permitido. Ex. erreeme arq*" exit 1 fi
MeuDir="/tmp/$LOGNAME" # Variavel do sist. Contém o nome do usuário. if [ ! -d $MeuDir ] # Se não existir o meu diretório sob o /tmp... then mkdir $MeuDir # Vou cria-lo fi
if [ ! -w $MeuDir ] # Se não posso gravar no diretório... then echo Impossivel salvar arquivos em $MeuDir. Mude permissao... exit 2 fi
Erro=0 # Variavel para indicar o cod. de retorno do prg for Arq # For sem o "in" recebe os parametros passados do if [ ! -f $Arq ] # Se este arquivo não existir... then echo $Arq nao existe. Erro=3 continue # Volta para o comando for fi
DirOrig=`dirname $Arq` # Cmd. dirname informa nome do dir de $Arq if [ ! -w $DirOrig ] # Verifica permissão de gravacaoo no diretório then echo Sem permissao de remover no diretorio de $Arq Erro=4 continue # Volta para o comando for fi
if [ "$DirOrig" = "$MeuDir" ] # Se estou "esvaziando a lixeira"... then echo $Arq ficara sem copia de seguranca rm -i $Arq # Pergunta antes de remover [ -f $Arq ] || echo $Arq removido # Será que o usuario removeu? continue fi
cd $DirOrig # Guardo no fim do arquivo o seu diretorio pwd >> $Arq # original para usa-lo em um script de undelete mv $Arq $MeuDir # Salvo e removo echo $Arq removido done exit $Erro # Passo eventual numero do erro para o codigo de retorno
Como você pode ver, a maior parte do script é formada por pequenas criticas aos parâmetros informados, mas como o script pode ter recebido diversos arquivos para remover, a cada arquivo que não se encaixa dentro do especificado, há um continue
, para que a sequência volte para o loop do for
de forma a receber outros arquivos.
Quando você está no Windows (com perdão da má palavra) e tenta remover aquele monte de lixo com nomes esquisitos como HD04TG.TMP
, se der erro em um deles, os outros não são removidos, não é? Então, o continue
foi usado para evitar que um impropério desses ocorra, isto é, mesmo que dê erro na remoção de um arquivo, o programa continuará removendo os outros que foram passados.
- Eu acho que a esta altura você deve estar curioso para ver o programa que restaura o arquivo removido, não é? Pois então aí vai vai um desafio: faça-o em casa e me traga para discutirmos no nosso próximo encontro aqui no boteco.
- Poxa, mas nesse eu acho que vou dançar, pois não sei nem como começar...
- Cara, este programa é como tudo que se faz em Shell, extremamente fácil, é para ser feito em, no máximo 10 linhas. Não se esqueça que o arquivo está salvo em /tmp/$LOGNAME
e que a sua última linha é o diretório em que ele residia antes de ser "removido". Também não se esqueça de criticar se foi passado o nome do arquivo a ser removido.
- É eu vou tentar, mas sei não...
- Tenha fé irmão, eu tô te falando que é mole! Qualquer dúvida é só me passar um e-mail para julio.neves@gmail.com. Agora chega de papo que eu já estou de goela seca de tanto falar. Me acompanha no próximo chope ou já vai sair correndo para fazer o script que passei?
- Deixa eu pensar um pouco...
- Chico, traz mais um chope enquanto ele pensa!
Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para a nossa gerencia de treinamento para informar-se.
Qualquer dúvida ou falta de companhia para um chope ou até para falar mal dos políticos é só mandar um e-mail para mim.
Valeu!