You are here:
Wiki-SL
>
TWikiBar Web
>
BatePapos
>
TWikiBarPapo006
(01 Aug 2009,
BrSalgado
)
(raw view)
E
dit
A
ttach
---+!! Papo de botequim parte VI %TOC% ---++ Comandos de _Loop_ ou Laço (Continuação) - Fala cara! E aí, já tá sabendo tudo do comando =for=? Eu te deixei um exercício para treinar, se não me engano era para contar a quantidade de palavras de um arquivo... Você fez? - Claro! Tô empolgadão com essa linguagem, eu fiz da forma que você pediu, isto é sem usar o comando =wc= porque senão era mais mole ainda. Olha só como eu fi... - Ê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... %TERMINAL_INI% $ cat contpal.sh%OUT_INI% #!/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, <TAB> ou <ENTER>. 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.%OUT_FIM% %TERMINAL_FIM% Ou seja, o programa começa como sempre verificando se a passagem de parâmetros foi correta, em seguida o comando =for= se incumbe de pegar cada uma das palavras (lembre-se que o =$IFS= padrão (default) é branco, =<TAB>= e =<ENTER>=, que é exatamente o que desejamos para separar as palavras), incrementando a variável =$Cont=. Vamos relembrar como é o arquivo =ArqDoDOS.txt=. %TERMINAL_INI% $ cat <nop>ArqDoDOS.txt%OUT_INI% Este arquivo foi gerado pelo DOS/Rwin e foi baixado por um ftp mal feito.%OUT_FIM% %TERMINAL_FIM% Agora vamos testar o programa passando este arquivo como parâmetro: %TERMINAL_INI% $ contpal.sh <nop>ArqDoDOS.txt%OUT_INI% O arquivo <nop>ArqDoDOS.txt tem 14 palavras%OUT_FIM% %TERMINAL_FIM% - Beleza, funcionou legal! ---+++ Um Pouco Mais de =for= e Matemática Voltando à vaca fria, na última vez que aqui estivemos, terminamos o nosso papo mostrando o _loop_ de =for= a seguir: <verbatim> for ((; i<=9;)) do let i++ echo -n "$i " done </verbatim> Uma vez que chegamos neste ponto, creio ser bastante interessante citar que o Shell trabalha com o conceito de "Expansão Aritmética" (_Arithmetic Expansion_), da qual vou falar rapidamente porque na seção <a href="https://twiki.softwarelivre.org/bin/view/TWikiBar/TWikiBarBirinaite#Aritm%E9tica_em_Shell" target="_blank">Tira Gosto</a> isso está muito bem mastigado. A expansão aritmética é acionada por uma construção da forma: =$((expressão))= ou =let expressão= No último =for= citado usei a expansão das duas formas, mas não poderíamos seguir adiante sem saber que a expressão pode ser de uma das listadas a seguir: <center> %TABLE{ sort="off" tableborder="0" cellpadding="4" cellspacing="1" headerbg="#0000FF" headercolor="#FFFF00" databg="#BBBBBB,#DDDDDD" headerrows="2" footerrows="1" }% | *Expansão Aritmética* || | *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 | | =||= | OU lógico | </center> - 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. ---+++ O comando =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: <verbatim> while comando do cmd1 cmd2 ... cmdn done </verbatim> 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: %TERMINAL_INI% $ cat logaute.sh%OUT_INI% #!/bin/bash # Espero que a Xuxa não tenha # copyright de xefe e xato <nop>:) while who | grep xefe do sleep 30 done echo O xato se mandou, não hesite, dê exit e vá a luta%OUT_FIM% %TERMINAL_FIM% 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? %TERMINAL_INI% $ logaute.sh%OUT_INI% xefe pts/0 Jan 4 08:46 (10.2.4.144) xefe pts/0 Jan 4 08:47 (10.2.4.144) ... xefe pts/0 Jan 4 08:52 (10.2.4.144)%OUT_FIM% %TERMINAL_FIM% 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=. %TERMINAL_INI% $ cat logaute.sh%OUT_INI% #!/bin/bash # Espero que a Xuxa não tenha # copyright de xefe e xato <nop>:) while who | grep xefe > /dev/null do sleep 30 done echo O xato se mandou, não hesite, dê exit e vá a luta%OUT_FIM% %TERMINAL_FIM% 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_: %TERMINAL_INI% $ sleep 10&%OUT_INI% [1] 16317%OUT_FIM% $ echo $!%OUT_INI% 16317 [1]+ Done sleep 10%OUT_FIM% $ echo $!%OUT_INI% 16317%OUT_FIM% %TERMINAL_FIM% 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: %TERMINAL_INI% $ cat monbg.sh%OUT_INI% #!/bin/bash # Executa e monitora um # processo em background $1 & # Coloca em backgroud while ps | grep -q $! do sleep 5 done echo Fim do Processo $1%OUT_FIM% %TERMINAL_FIM% 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. %DICA_INI% Não esqueça: o _Bash_ disponibiliza a variável =$!= que possui o =PID= (_Process IDentification_) do último processo executado em _background_. %DICA_FIM% 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: %TERMINAL_INI% $ read -p "prompt de leitura" var %TERMINAL_FIM% 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: %TERMINAL_INI% $ read -p "Título do Álbum: " Tit %TERMINAL_FIM% 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: %TERMINAL_INI% $ cat musinc%OUT_INI% #!/bin/bash # Cadastra CDs (versao 4) # clear read -p "Título do Álbum: " Tit [ "$Tit" ] || exit 1 # Fim da execução se título vazio if grep "^$Tit\^" musicas > /dev/null then echo Este álbum já está cadastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= while true do echo Dados da trilha $Cont: read -p "Música: " Mus [ "$Mus" ] || break # Sai se vazio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se vazio Art anterior Reg="$Reg$oArt~$Mus:" # Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas%OUT_FIM% %TERMINAL_FIM% 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. %DICA_INI% Leitura de arquivo significa ler um-a-um todos os registros, o que é sempre uma operação lenta. Fique atento para não usar o =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). %DICA_FIM% ---+++ O comando =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: <verbatim> until comando do cmd1 cmd2 ... cmdn done </verbatim> 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: %TERMINAL_INI% $cat chegada.sh%OUT_INI% #!/bin/bash until who | grep julio do sleep 30 done echo $(date "+ Em %d/%m às %H:%Mh") >> relapso.log%OUT_FIM% %TERMINAL_FIM% 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: %TERMINAL_INI% $ cat musinc%OUT_INI% #!/bin/bash # Cadastra CDs (versao 5) # Para= until [ "$Para" ] do clear read -p "Título do Álbum: " Tit if [ ! "$Tit" ] # Se titulo vazio... then Para=1 # Liguei flag de saída else if grep "^$Tit\^" musicas > /dev/null then echo Este álbum já está cadastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= while [ "$Tit" ] do echo Dados da trilha $Cont: read -p "Música: " Mus [ "$Mus" ] || break # Sai se vazio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se vazio Art anterior Reg="$Reg$oArt~$Mus:" # Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas fi done%OUT_FIM% %TERMINAL_FIM% 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. ---+++ Atalhos no loop 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. <center> <img src="%PUBURL%/%WEB%/FreeSkinImagens/Fluxograma.jpg" alt="Fluxograma" /> </center> 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: %TERMINAL_INI% $ cat erreeme%OUT_INI% #/bin/bash # # Salvando Copia de Arquivo Antes de Remove-lo # 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 <nop>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%OUT_FIM% %TERMINAL_FIM% 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 <a href="mailto:julio.neves@gmail.com?Subject=Duvidas Papo de botequim">julio.neves@gmail.com</a>. 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 <a href="mailto:contato@clavis.com.br?Subject=Curso de Shell com Julio Neves">gerencia de treinamento</a> 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 <a href="mailto:julioneves@openoffice.org?Subject=Dúvidas Papo de Botequim">mim</a>. Valeu!
E
dit
|
A
ttach
|
P
rint version
|
H
istory
: r20
<
r19
<
r18
<
r17
<
r16
|
B
acklinks
|
V
iew topic
|
M
ore topic actions
Topic revision: r20 - 01 Aug 2009 - 22:33:09 -
BrSalgado
TWikiBar
Página Inicial
Últimas alterações
Índice
Procurar
Estatísticas de Uso
Aviso de Atualização
Configurações Gerais
Projeto Gráfico
Mapa do Site
Quem Somos
Registre-se
?
Regras de Formatação
Biblioteca Gráfica
?
Carinhas Gráficas
Webs Wiki-SL
Amadeu
Anapolivre
ArquivoLivre
Arte
BahiaSocial
BeaBa
BibliotecaLivre
Blogs
BrasilDigital
BrasilELivre
BSM
Ccsa
CESL
CoberturaWiki
Cooperativas
Curriculo
DarvinMarosin
DiaD
Dinamicoop
Economia
EconomiaSolidaria
EducacaoLivre
Ekaaty
Emacsbr
ENSL
Fatos
Festival3
Festival4
Flisol
Fmpb
Formatos
Foswikibr
FSM2005
GNOMEBR
GTTemario2004
GTWeb
Guialivre
HDC
Incubus
InkscapeBrasil
Jogos
KdeBR
KSP
LGM
LinuxStokDoc
Livros
Main
Mentores
MHHOB
MinuanoDigital
MoradiaECidadania
OlhosDagua
Olimpo
OLPC
OOPTQ
Papers
PCLivre
PentahoBrasil
Pessoas
Portal
Prefeituras
PSLAL
PSLBA
PSLBancarios
PSLBrasil
PSLGO
PSLMA
PSLMG
PSLMIP
PSLMT
PSLMulheres
PSLPI
PubFisl10
PubFisl7
PubFisl8
PubFisl9
QuilomboDoSopapo
RadioSL
RedeMesh
RedePopular
RobotWars
Sandbox
Saudelivre
Scribus
Sementes
Shakya
SLRJ
SoftwareLivreIrece
SoftwareLivreVS
SoLiSC
SuporteLivre
System
Telecentros
TeseSA
TextoLivre
TV
TWikiBar
TWikiPtbr
UNELivre
UNIMIX
VilaTorres
WebNordeste
WTRD2004
Este Menu
?skin=free
English
Español
Português brasileiro
Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Wiki-SL?
Send feedback