Feeds:
Posts
Comentários

Posts Tagged ‘pipeline’

Papo de Botequim – Parte 6

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…

$ 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, ou .

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 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.

$ 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 for e Matemática

Voltando à vaca fria, na última vez que aqui estivemos, terminamos o nosso papo mostrando o loop de for a seguir:

    for ((; i<=9;))
    do
        let i++
        echo -n "$i "
    done

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 Tira Gosto 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:

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

– 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:

    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:

$ cat logaute.sh
#!/bin/bash

# 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?

$ logaute.sh
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)

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.

$ cat logaute.sh
#!/bin/bash

# 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:

$ sleep 10&
[1] 16317

$ echo $!
16317
[1]+ Done sleep 10

$ echo $!
16317

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:

$ cat monbg.sh
#!/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

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.

Pinguim com placa de dica Não esqueça: o Bash disponibiliza a variável $! 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:

$ read -p “prompt de leitura” var

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:

$ read -p “Título do Álbum: ” Tit

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:

$ cat musinc
#!/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

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.

Pinguim com placa de dica 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).

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:

    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:

$cat chegada.sh
#!/bin/bash

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:

$ cat musinc
#!/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

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.

Fluxograma 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:

$ cat erreeme
#/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

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!

Anúncios

Read Full Post »