Feeds:
Posts
Comentários

Posts Tagged ‘do’

Papo de Botequim – Parte 4

– E aí cara, tentou fazer o exercício que te pedi para revigorar as idéias?

– Claro, que sim! Em programação, se você não treinar, não aprende. Você me pediu para fazer um scriptizinho para informar se um determinado usuário, que será passado como parâmetro esta logado (arghh!) ou não. Eu fiz o seguinte:

$ cat logado
#!/bin/bash
# Pesquisa se uma pessoa está logada ou não
if who | grep $1
then
echo $1 está logado
else
echo $1 não se encontra no pedaço
fi

– Calma rapaz! Já vi que você chegou cheio de tesão, primeiro vamos pedir os nossos chopes de praxe e depois vamos ao Shell. Chico traz dois chopes, um sem colarinho!

– Agora que já molhamos os nossos bicos, vamos dar uma olhadinha na execução do seu bacalho:

$ logado jneves
jneves pts/0 Oct 18 12:02 (10.2.4.144)
jneves está logado

Realmente funcionou. Passei o meu login como parâmetro e ele disse que eu estava logado, porém ele mandou uma linha que eu não pedi. Esta linha é a saída do comando who, e para evitar que isso aconteça é só mandá-la para o buraco negro que a esta altura você já sabe que é o /dev/null. Vejamos então como ficaria:

$ cat logado
#!/bin/bash
# Pesquisa se uma pessoa está logada ou não (versão 2)
if who | grep $1 > /dev/null
then
echo $1 está logado
else
echo $1 não se encontra no pedaço
fi

Agora vamos aos testes:

$ logado jneves
jneves está logado
$ logado chico
chico não se encontra no pedaço
Pinguim com placa de atenção Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos tem uma saída padrão e uma saída de erros (o grep é uma das poucos exceções, já que não dá mensagem de erro quando não acha uma cadeia) e é necessário estarmos atentos para redirecioná-las para o buraco negro quando necessário.

Bem, agora vamos mudar de assunto: na última vez que nos encontramos aqui no Botequim, eu estava te mostrando os comandos condicionais e, quando já estávamos de goela seca falando sobre o if, você me perguntou como se testa condições. Vejamos então o

O Comando test

Bem, todos estamos acostumados a usar o if testando condições, e estas são sempre, maior, menor, maior ou igual, menor ou igual, igual e diferente. Bem, em Shell para testar condições, usamos o comando test, só que ele é muito mais poderoso que o que estamos habituados. Primeiramente vou te mostrar as principais opções (existem muitas outras) para testarmos arquivos em disco:

Opções do Comando test para arquivos
Opção Verdadeiro se:
-e arq arq existe
-s arq arq existe e tem tamanho maior que zero
-f arq arq existe e é um arquivo regular
-d arq arq existe e é um diretório;
-r arq arq existe e com direito de leitura
-w arq arq existe e com direito de escrita
-x arq arq existe e com direito de execução

Veja agora as principais opções para teste de cadeias de caracteres:

Opções do comando test para cadeias de caracteres
Opção Verdadeiro se:
-z cadeia Tamanho de cadeia é zero
-n cadeia Tamanho de cadeia é maior que zero
cadeia A cadeia cadeia tem tamanho maior que zero
c1 = c2 Cadeia c1 e c2 são idênticas

E pensa que acabou? Engano seu! Agora é que vem o que você está mais acostumado, ou seja as famosas comparações com numéricos. Veja a tabela:

Opções do comando test para números
Opção Verdadeiro se: Significado
n1 -eq n2 n1 e n2 são iguais equal
n1 -ne n2 n1 e n2 não são iguais not equal
n1 -gt n2 n1 é maior que n2 greater than
n1 -ge n2 n1 é maior ou igual a n2 greater or equal
n1 -lt n2 n1 é menor que n2 less than
n1 -le n2 n1 é menor ou igual a n2 less or equal

Além de tudo, some-se a estas opções as seguintes facilidades:

Operadores
Operador Finalidade
Parênteses ( ) Agrupar
Exclamação ! Negar
-a E lógico
-o OU lógico

Ufa! Como você viu tem coisa prá chuchu, e como eu te disse no início, o nosso if é muito mais poderoso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramente testaremos a existência de um diretório:

Exemplos:

    if  test -d lmb
    then
        cd lmb
    else
        mkdir lmb
        cd lmb
    fi

No exemplo, testei se existia um diretório lmb definido, caso negativo (else), ele seria criado. Já sei, você vai criticar a minha lógica dizendo que o script não está otimizado. Eu sei, mas queria que você o entendesse assim, para então poder usar o ponto-de-espantação (!) como um negador do test. Veja só:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

Desta forma o diretório lmb seria criado somente se ele ainda não existisse, e esta negativa deve-se ao ponto-de-exclamação (!) precedendo a opção -d. Ao fim da execução deste fragmento de script, o programa estaria com certeza dentro do diretório lmb.

Vamos ver dois exemplos para entender a diferença comparação entre números e entre cadeias.

    cad1=1
    cad2=01
    if  test $cad1 = $cad2
    then
        echo As variáveis são iguais.
    else
        echo As variáveis são diferentes.
    fi

Executando o fragmento de programa acima vem:

As variáveis são diferentes.

Vamos agora alterá-lo um pouco para que a comparação seja numérica:

    cad1=1
    cad2=01
    if  test $cad1 -eq $cad2
    then
        echo As variáveis são iguais.
    else
        echo As variáveis são diferentes.
    fi

E vamos executá-lo novamente:

As variáveis são iguais.

Como você viu nas duas execuções obtive resultados diferentes porque a cadeia 01 é realmente diferente da cadeia 1, porém, a coisa muda quando as variáveis são testadas numericamente, já que o número 1 é igual ao número 01.

Exemplos:

Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo animal feito direto no prompt (me desculpem os zoólogos, mas eu não entendendo nada de reino, filo, classe, ordem, família, gênero e espécie, desta forma o que estou chamando de família ou de gênero tem grande chance de estar incorreto):

$ Familia=felinae
$ Genero=gato
$ if test $Familia = canidea -a $Genero = lobo -o $Familia = felina -a $Genero = leão
> then
> echo Cuidado
> else
> echo Pode passar a mão
> fi
Pode passar a mão

Neste exemplo caso o animal fosse da família canídea E (-a) do gênero lobo, OU (-o) da familia felina E (-a) do gênero leão, seria dado um alerta, caso contrário a mensagem seria de incentivo.

Pinguim com placa de dica Os sinais de maior (>) no início das linhas internas ao if são os prompts de continuação (que estão definidos na variável $PS2) e quando o Shell identifica que um comando continuará na linha seguinte, automaticamente ele o coloca até que o comando seja encerrado.

Vamos mudar o exemplo para ver se continua funcionando:

$ Familia=felino
$ Genero=gato
$ if test $Familia = felino -o $Familia = canideo -a $Genero = onça -o $Genero = lobo
> then
> echo Cuidado
> else
> echo Pode passar a mão
> fi
Cuidado

Obviamente a operação redundou em erro, isto foi porque a opção -a tem precedência sobre a -o, e desta forma o que primeiro foi avaliado foi a expressão:

$Familia = canideo -a $Genero = onça

Que foi avaliada como falsa, retornando o seguinte:

$Familia = felino -o FALSO -o $Genero = lobo

Que resolvida vem:

VERDADEIRO -o FALSO -o FALSO

Como agora todos conectores são -o, e para que uma série de expressões conectadas entre si por diversos OU lógicos seja verdadeira, basta que uma delas seja, a expressão final resultou como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar façamos o seguinte:

$ if test ($Familia = felino -o $Familia = canideo) -a ($Genero = onça -o $Genero = lobo)
> then
> echo Cuidado
> else
> echo Pode passar a mão
> fi
Pode passar a mão

Desta forma, com o uso dos parênteses agrupamos as expressões com o conector -o, priorizando as suas execuções e resultando:

VERDADEIRO -a FALSO

Para que seja VERDADEIRO o resultado duas expressões ligadas pelo conector -a é necessário que ambas sejam verdadeiras, o que não é o caso do exemplo acima. Assim o resultado final foi FALSO sendo então o else corretamente executado.

Se quisermos escolher um CD que tenha faixas de 2 artistas diferentes, nos sentimos tentados a usar um if com o conector -a, mas é sempre bom lembrarmos que o bash nos dá muito recursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, da seguinte maneira:

$ grep Artista1 musicas | grep Artista2

Da mesma forma para escolhermos CDs que tenham a participação do Artista1 e do Artista2, não é necessário montarmos um if com o conector -o. O egrep (ou grep -E, sendo este mais aconselhável) também resolve isso para nós. Veja como:

$ egrep (Artista1|Artista2) musicas

Ou (nesse caso específico) o próprio grep puro e simples poderia nos quebrar o galho:

$ grep Artista[12] musicas

No egrep acima, foi usada uma expressão regular, onde a barra vertical (|) trabalha como um OU lógico e os parênteses são usados para limitar a amplitude deste OU. Já no grep da linha seguinte, a palavra Artista deve ser seguida por um dos valores da lista formada pelos colchetes ([ ]), isto é, 1 ou 2.

– Tá legal, eu aceito o argumento, o if do Shell é muito mais poderoso que os outros caretas, mas cá pra nós, essa construção de if test ... é muito esquisita, é pouco legível.

– É você tem razão, eu também não gosto disso e acho que ninguém gosta. Acho que foi por isso, que o Shell incorporou outra sintaxe que substitui o comando test.

Exemplos:

Para isso vamos pegar aquele exemplo para fazer uma troca de diretórios, que era assim:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

e utilizando a nova sintaxe, vamos fazê-lo assim:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

Ou seja, o comando test pode ser substituído por um par de colchetes ([ ]), separados por espaços em branco dos argumentos, o que aumentará enormemente a legibilidade, pois o comando if irá ficar com a sintaxe semelhante à das outras linguagens e por isso este será o modo que o comando test será usado daqui para a frente.

Querida, Encolheram o Comando Condicional

Se você pensa que acabou, está muito enganado. Repare a tabela (tabela verdade) a seguir:

Valores Booleanos E OU
VERDADEIRO-VERDADEIRO VERDADEIRO VERDADEIRO
VERDADEIRO-FALSO FALSO VERDADEIRO
FALSO-VERDADEIRO FALSO VERDADEIRO
FALSO-FALSO FALSO FALSO

Ou seja, quando o conector é E e a primeira condição é verdadeira, o resultado final pode ser VERDADEIRO ou FALSO, dependendo da segunda condição, já no conector OU, caso a primeira condição seja verdadeira, o resultado sempre será VERDADEIRO e se a primeira for falsa, o resultado dependerá da segunda condição.

Ora, os caras que desenvolveram o interpretador não são bobos e estão sempre tentando otimizar ao máximo os algoritmos. Portanto, no caso do conector E, a segunda condição não será avaliada, caso a primeira seja falsa, já que o resultado será sempre FALSO. Já com o OU, a segunda será executada somente caso a primeira seja falsa.

Aproveitando disso, criaram uma forma abreviada de fazer testes. Batizaram o conector E de && e o OU de || e para ver como isso funciona, vamos usá-los como teste no nosso velho exemplo de trocarmos de diretório, que em sua última versão estava assim:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

Isso também poderia ser escrito da seguinte maneira:

    [ ! -d lmb ] && mkdir lmb
    cd lmb

Ou ainda retirando a negação (!):

    [ -d lmb ] || mkdir lmb
    cd lmb

No primeiro caso, se o primeiro comando (o test que está representado pelos colchetes) for bem sucedido, isto é, não existir o diretório lmb, o mkdir será efetuado porque a primeira condição era verdadeira e o conector era E.

No exemplo seguinte, testamos se o diretório lmb existia (no anterior testamos se ele não existia) e caso isso fosse verdade, o mkdir não seria executado porque o conector era OU. Outra forma:

cd lmb || mkdir lmb

Neste caso, se o cd fosse mal sucedido, seria criado o diretório lmb mas não seria feito o cd para dentro dele. Para executarmos mais de um comando desta forma, é necessário fazermos um grupamento de comandos, e isso se consegue com o uso de chaves ({ }). Veja como seria o correto:

    cd lmb ||
        {
        mkdir lmb
        cd lmb
        }

Ainda não está legal, porque caso o diretório não exista, o cd dará a mensagem de erro correspondente. Então devemos fazer:

    cd lmb 2> /dev/null ||
        {
        mkdir lmb
        cd lmb
        }

Como você viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. É sempre bom lembrarmos que o seguro a que me referi é no tocante ao fato de que ao final da execução você sempre estará dentro de lmb, desde que você tenha permissão entrar em lmb, permissão para criar um diretório em ../lmb, haja espaço em disco, …

E tome de test

Ufa! Você pensa que acabou? Ledo engano! Ainda tem uma forma de test a mais. Essa é legal porque ela te permite usar padrões para comparação. Estes padrões atendem às normas de Geração de Nome de Arquivos (File Name Generation, que são ligeiramente parecidas com as Expressões Regulares, mas não podem ser confundidas com estas). A diferença de sintaxe deste para o test que acabamos de ver é que esse trabalha com dois pares de colchete da seguinte forma:

[[ expressão ]]

Onde expressão é uma das que constam na tabela a seguir:

Expressões Condicionais Para Padrões
Expressão Retorna
cadeia == padrão
cadeia1 = padrao
Verdadeiro se cadeia1 casa com padrão
cadeia1 != padrao Verdadeiro se cadeia1 não casa com padrao.
cadeia1 < cadeia1 Verdadeiro se cadeia1 vem antes de cadeia1 alfabeticamente.
cadeia1 > cadeia1 Verdadeiro se cadeia1 vem depois de cadeia1 alfabeticamente
expr1 && expr2 “E” lógico, verdadeiro se ambos expr1 e expr2 são verdadeiros
expr1 ¦¦ expr2 “OU” lógico, verdadeiro se expr1 ou expr2 for verdadeiro
$ echo $H
13

$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida
Hora inválida

$H=12
$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida
$

Neste exemplo, testamos se o conteúdo da variável $H estava compreendido entre zero e nove ([0-9]) ou (||) se estava entre dez e doze (1[0-2]), dando uma mensagem de erro caso não fosse.

Exemplos:

Para saber se uma variável tem o tamanho de um e somente um caractere, faça:

$ var=a
$ [[ $var == ? ]] && echo var tem um caractere
var tem um caractere

$ var=aa
$ [[ $var == ? ]] && echo var tem um caractere
$

Como você pode imaginar, este uso de padrões para comparação, aumenta muito o poderio do comando test. No início deste papo, antes do último chope, afirmamos que o comando if do interpretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemos todo o seu espectro de funções, diga-me: você concorda ou não com esta assertiva?

Acaso Casa com case

Vejamos um exemplo didático: dependendo do valor da variável $opc o script deverá executar uma uma das opções: inclusão, exclusão, alteração ou fim. Veja como ficaria este fragmento de script:

    if  [ $opc -eq 1 ]
    then
        inclusao
    elif [ $opc -eq 2 ]
    then
        exclusao
    elif [ $opc -eq 3 ]
    then
        alteracao
    elif [ $opc -eq 4 ]
    then
        exit
    else
        echo Digite uma opção entre 1 e 4
    fi

Neste exemplo você viu o uso do elif com um else if, esta á a sintaxe válida e aceita, mas poderíamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir:

    case $var in
        padrao1) cmd1
                 cmd2
                 cmdn ;;
        padrao2) cmd1
                 cmd2
                 cmdn ;;
        padraon) cmd1
                 cmd2
                 cmdn ;;
    esac

Onde a variável $var é comparada aos padrões padrao1, ..., padraon e caso um deles atenda, o bloco de comandos cmd1, ..., cmdn correspondente é executado até encontrar um duplo ponto-e-vírgula (;;), quando o fluxo do programa se desviará para instrução imediatamente após o esac.

Na formação dos padrões, são aceitos os seguintes caracteres:

Caracteres Para Formação de Padrões
Caractere Significado
* Qualquer caractere ocorrendo zero ou mais vezes
? Qualquer caractere ocorrendo uma vez
[...] Lista de caracteres
¦ OU lógico

Para mostrar como fica melhor, vamos repetir o exemplo anterior, só que desta vez usaremos o case e não o if ... elif ... else ... fi.

    case $opc in
        1) inclusao ;;
        2) exclusao ;;
        3) alteracao ;;
        4) exit ;;
        *) echo Digite uma opção entre 1 e 4
    esac

Como você deve ter percebido, eu usei o asterisco como a última opção, isto é, se o asterisco atende a qualquer coisa, então ele servirá para qualquer coisa que não esteja no intervalo de 1 a 4. Outra coisa a ser notada é que o duplo ponto-e-vírgula não é necessário antes do esac.

Exemplos:

Vamos agora fazer um script mais radical. Ele te dará bom dia, boa tarde ou boa noite dependendo da hora que for executado, mas primeiramente veja estes comandos:

$ date
Tue Nov 9 19:37:30 BRST 2004

$ date +%H
19

O comando date informa a data completa do sistema, mas ele tem diversas opções para seu mascaramento. Neste comando, a formatação começa com um sinal de mais (+) e os caracteres de formatação vêm após um sinal de percentagem (%), assim o %H significa a hora do sistema. Dito isso vamos ao exemplo:

$ cat boasvindas.sh
#!/bin/bash
# Programa bem educado que
# dá bom-dia, boa-tarde ou
# boa-noite conforme a hora
Hora=$(date +%H)
case $Hora in
0? | 1[01]) echo Bom Dia
;;
1[2-7] ) echo Boa Tarde
;;
* ) echo Boa Noite
;;
esac
exit

Peguei pesado, né? Que nada vamos esmiuçar a resolução caso-a-caso (ou seria case-a-case? smile )

0? | 1[01] – Significa zero seguido de qualquer coisa (?), ou (|) um seguido de zero ou um ([01]) ou seja, esta linha pegou 01, 02, … 09, 10 e 11;

1[2-7] – Significa um seguido da lista de dois a sete, ou seja, esta linha pegou 12, 13, … 17;

* – Significa tudo que não casou com nenhum dos padrões anteriores.

– Cara, até agora eu falei muito e bebi pouco. Agora eu vou te passar um exercício para você fazer em casa e me dar a resposta da próxima vez que nos encontrarmos aqui no botequim, tá legal?

– Tá, mas antes informe ao pessoal que está acompanhando este curso conosco como eles podem te encontrar para fazer críticas, contar piada, convidar para o chope, curso ou palestra ou até mesmo para falar mal dos políticos.

– É fácil, meu e-mail é julio.neves@gmail.com, mas pare de me embromar que eu não vou esquecer de te passar o script para fazer. É o seguinte: quero que você faça um programa que receberá como parâmetro o nome de um arquivo e que quando executado salvará este arquivo com o nome original seguido de um til (~) e colocará este arquivo dentro do vi (o melhor editor que se tem notícia) para ser editado. Isso é para ter sempre a última cópia boa deste arquivo caso o cara faça alterações indevidas. Obviamente, você fará as críticas necessárias, como verificar se foi passado um parâmetro, se o arquivo passado existe, … Enfim, o que te der na telha e você achar que deve constar do script. Deu prá entender?

– Hum, hum…

– Chico! Traz mais um sem colarinho que o cara aqui já está dando para entender! smile

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!

Read Full Post »

Papo de Botequim – Parte 7


– Cumequié rapaz! Derreteu os pensamentos para fazer o scriptizinho que eu te pedi? – É, eu realmente tive de colocar muita pensação na tela preta mas acho que consegui! Bem, pelo menos no testes que fiz a coisa funcionou, mas você tem sempre que botar chifres em cabeça de cachorro!- Não é bem assim, programar em shell é muito fácil, o que vale são as dicas e macetes que não são triviais. As correções que te faço, são justamente para mostrá-los. Mas vamos pedir dois chopes enquanto dou uma olhadela no teu script.- Aê Chico, traz dois. Não esqueça que um é sem colarinho.


$ cat restaura

#!/bin/bash
#
# Restaura arquivos deletados via erreeme
#

if [ $# -eq 0 ]
then
echo “Uso: $0 ”
exit 1
fi
# Pega nome do diretório original na última linha
Dir=`tail -1 /tmp/$LOGNAME/$1`
# O grep -v exclui última linha e cria o
# arquivo com diretorio e nome originais
grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1
# Remove arquivo que jah estava moribundo
rm /tmp/$LOGNAME/$1

– Peraí, deixa ver se entendi. Primeiramente você coloca na variável Dir a última linha do arquivo cujo nome é formado por /tmp/nome do operador ($LOGNAME)/parâmetro passado com nome do arquivo a ser restaurado ($1). Em seguida o grep -v que você montou exclui a linha em que estava o nome do diretório, isto é, sempre a última e manda o restante do arquivo, que seria assim o arquivo já limpo, para o diretório original e depois remove o arquivo da “lixeira”; S E N S A C I O N A L! Impecável! Zero erro! Viu? você já está pegando as manhas do shell!

– Então vamulá chega de lesco-lesco e blá-blá-blá, de que você vai falar hoje?

– É tô vendo que o bichinho do Shell te pegou. Que bom, mas vamos ver como se pode (e deve) ler dados e formatar telas e primeiramente vamos entender um comando que te dá todas as ferramentas para você formatar a sua tela de entrada de dados.

O comando tput

O maior uso deste comando é para posicionar o cursor na tela, mas também é muito usado para apagar dados da tela, saber a quantidade de linhas e colunas para poder posicionar corretamente um campo, apagar um campo cuja crítica detectou como errado. Enfim, quase toda a formatação da tela é feita por este comando.

Uns poucos atributos do comando tput podem eventualmente não funcionar se o modelo de terminal definido pela variável $TERM não tiver esta facilidade incorporada.

Na tabela a seguir, apresenta os principais atributos do comando e os efeitos executados sobre o terminal, mas veja bem existem muito mais do que esses, veja só:

$ tput it
8

Neste exemplo eu recebi o tamanho inicial da <TAB> ( Initial T ab), mas me diga: para que eu quero saber isso? Se você quiser saber tudo sobre o comando tput (e olha que é coisa que não acaba mais), veja em: http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4.

Principais Opções do Comando tput
Opções do tput Efeito
cup lin col CUrsor Position – Posiciona o cursor na linha lin e coluna col. A origem é zero
bold Coloca a tela em modo de ênfase
rev Coloca a tela em modo de vídeo reverso
smso Idêntico ao anterior
smul A partir desta instrução, os caracteres teclados aparecerão sublinhados na tela
blink Os caracteres teclados aparecerão piscando
sgr0 Após usar um dos atributos acima, use este para restaurar a tela ao seu modo normal
reset Limpa o terminal e restaura suas definições de acordo com o terminfo ou seja, o terminal volta ao padrão definido pela variável $TERM
lines Devolve a quantidade de linhas da tela no momento da instrução
cols Devolve a quantidade de colunas da tela no momento da instrução
el Erase Line – Apaga a linha a partir da posição do cursor
ed Erase Display – Apaga a tela a partir da posição do cursor
il n Insert Lines – Insere n linhas a partir da posição do cursor
dl n Delete Lines – Remove n linhas a partir da posição do cursor
ech n Erase CHaracters – Apaga n caracteres a partir da posição do cursor
sc Save Cursor position – Salva a posição do cursor
rc Restore Cursor position – Coloca o cursor na posição marcada pelo último sc

Vamos fazer um programa bem besta (e portanto fácil) para mostrar alguns atributos deste comando. É o famoso e famigerado Alô Mundo só que esta frase será escrita no centro da tela e em vídeo reverso e após isso, o cursor voltará para a posição em que estava antes de escrever esta tão criativa frase. Veja:

$ cat alo.sh
#!/bin/bash
# Script bobo para testar
# o comando tput (versao 1)

Colunas=`tput cols` # Salvando quantidade colunas
Linhas=`tput lines` # Salvando quantidade linhas
Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela?
Coluna=$(((Colunas – 9) / 2)) # Centrando a mensagem na tela
tput sc # Salvando posicao do cursor
tput cup $Linha $Coluna # Posicionando para escrever
tput rev # Video reverso
echo Alô Mundo
tput sgr0 # Restaura video ao normal
tput rc # Restaura cursor aa posição original

Como o programa já está todo comentado, acho que a única explicação necessária seria para a linha em que é criada a variável Coluna e o estranho ali é aquele número 9, mas ele é o tamanho da cadeia que pretendo escrever (Alô Mundo).

Desta forma este programa somente conseguiria centrar cadeias de 9 caracteres, mas veja isso:

$ var=Papo
$ echo ${#var}
4

$ var=”Papo de Botequim”
$ echo ${#var}
16

Ahhh, melhorou! Então agora sabemos que a construção ${#variavel} devolve a quantidade de caracteres de variavel. Assim sendo, vamos otimizar o nosso programa para que ele escreva em vídeo reverso, no centro da tela a cadeia passada como parâmetro e depois o cursor volte à posição que estava antes da execução do script.

$ cat alo.sh
#!/bin/bash
# Script bobo para testar
# o comando tput (versao 2)

Colunas=`tput cols` # Salvando quantidade colunas
Linhas=`tput lines` # Salvando quantidade linhas
Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela?
Coluna=$(((Colunas – ${#1}) / 2)) #Centrando a mensagem na tela
tput sc # Salvando posicao do cursor
tput cup $Linha $Coluna # Posicionando para escrever
tput rev # Video reverso
echo $1
tput sgr0 # Restaura video ao normal
tput rc # Restaura cursor aa posição original

Este script é igual ao anterior, só que trocamos o valor fixo da versão anterior (9), por ${#1}, onde este 1 é o $1 ou seja, esta construção devolve o tamanho do primeiro parâmetro passado para o programa. Se o parâmetro que eu quiser passar tiver espaços em branco, teria que colocá-lo todo entre aspas, senão o $1 seria somente o primeiro pedaço. Para evitar este aborrecimento, é só substituir o $1 por $*, que como sabemos é o conjunto de todos os parâmetros. Então aquela linha ficaria assim:

    Coluna=`$(((Colunas - ${#*}) / 2))` #Centrando a mensagem na tela

e a linha echo $1 passaria a ser echo $*. Mas não esqueça de qdo executar, passar a frase que vc desja centrar como parâmetro.

E agora podemos ler os dados da tela

Bem a partir de agora vamos aprender tudo sobre leitura, só não posso ensinar a ler cartas e búzios porque se eu soubesse, estaria rico, num pub londrino tomando scotch e não em um boteco desses tomando chope. Mas vamos em frente.

Da última vez que nos encontramos aqui eu já dei uma palinha sobre o comando read. Para começarmos a sua analise mais detalhada. veja só isso:

$ read var1 var2 var3
Papo de Botequim

$ echo $var1
Papo

$ echo $var2
de

$ echo $var3
Botequim

$ read var1 var2
Papo de Botequim

$ echo $var1
Papo

$ echo $var2
de Botequim

Como você viu, o read recebe uma lista separada por espaços em branco e coloca cada item desta lista em uma variável. Se a quantidade de variáveis for menor que a quantidade de itens, a última variável recebe o restante.

Eu disse lista separada por espaços em branco? Agora que você já conhece tudo sobre o $IFS (Inter Field Separator) que eu te apresentei quando falávamos do comando for, será que ainda acredita nisso? Vamos testar direto no prompt:

$ oIFS=”$IFS”
$ IFS=:
$ read var1 var2 var3
Papo de Botequim

$ echo $var1
Papo de Botequim

$ echo $var2$ echo $var3
$ read var1 var2 var3
Papo:de:Botequim

$ echo $var1
Papo

$ echo $var2
de

$ echo $var3
Botequim

$ IFS=”$oIFS”

Viu, estava furado! O read lê uma lista, assim como o for, separada pelos caracteres da variável $IFS. Então veja como isso pode facilitar a sua vida:

$ grep julio /etc/passwd
julio:x:500:544:Julio C. Neves – 7070:/home/julio:/bin/bash

$ oIFS=”$IFS” # Salvando IFS
$ IFS=:
$ grep julio /etc/passwd | read lname lixo uid gid coment home shell
$ echo -e “$lnamen$uidn$gidn$comentn$homen$shell”
julio
500
544
Julio C. Neves – 7070
/home/julio
/bin/bash

$ IFS=”$oIFS” # Restaurando IFS

Como você viu, a saída do grep foi redirecionada para o comando read que leu todos os campos de uma só tacada. A opção -e do echo foi usada para que o n fosse entendido como um salto de linha (new line), e não como um literal.

Sob o Bash existem diversas opções do read que servem para facilitar a sua vida. Veja a tabela a seguir:

Opções do comando read no Bash
Opção Ação
-p prompt Escreve o prompt antes de fazer a leitura
-n num Lê até num caracteres
-t seg Espera seg segundos para que a leitura seja concluída
-s O que está sendo teclado não aparece na tela

E agora direto aos exemplos curtos para demonstrar estas opções.

Para ler um campo “Matrícula”:

$ echo -n “Matricula: “; read Mat # -n nao salta linha
Matricula: 12345

$ echo $Mat
12345

Ou simplificando com a opção -p:

$ read -p “Matricula: ” Mat
Matricula: 12345

$ echo $Mat
12345

Para ler uma determinada quantidade de caracteres:

$ read -n5 -p”CEP: ” Num ; read -n3 -p- Compl
CEP: 12345-678
$
$ echo $Num
12345

$ echo $Compl
678

Neste exemplo fizemos dois read: um para a primeira parte do CEP e outra para o seu complemento, deste modo formatando a entrada de dados. O cifrão ($) após o último algarismo teclado, é porque o read não tem o new-line implícito por default como o tem o echo.

Para ler que até um determinado tempo se esgote (conhecido como time out):

$ read -t2 -p “Digite seu nome completo: ” Nom || echo ‘Eta moleza!’
Digite seu nome completo: JEta moleza!

$ echo $Nom$

Obviamente isto foi uma brincadeira, pois só tinha 3 segundos para digitar o meu nome completo e só me deu tempo de teclar um J (aquele colado no Eta), mas serviu para mostrar duas coisas:

  1. O comando após o par de barras verticais (||) (o ou lógico, lembra-se?) será executado caso a digitação não tenha sido concluída no tempo estipulado;
  2. A variável Nom permaneceu vazia. Ela será valorada somente quando o <ENTER> for teclado.

Para ler um dado sem ser exibido na tela:

$ read -sp “Senha: “
Senha:
$ echo $REPLY
segredo 🙂

Aproveitei um erro para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocar o nome da variável que iria receber a senha, e só notei quando ia listar o seu valor. Felizmente a variável $REPLY do Bash, possui a última cadeia lida e me aproveitei disso para não perder a viagem. Teste você mesmo o que acabei de fazer.

Mas o exemplo que dei, era para mostrar que a opção -s impede o que está sendo teclado de ir para a tela. Como no exemplo anterior, a falta do new-line fez com que o prompt de comando ($) permanecesse na mesma linha.

Bem, agora que sabemos ler da tela vejamos como se lê os dados dos arquivos.

Vamos ler arquivos?

Como eu já havia lhe dito, e você deve se lembrar, o while testa um comando e executa um bloco de instruções enquanto este comando for bem sucedido. Ora quando você está lendo um arquivo que lhe dá permissão de leitura, o read só será mal sucedido quando alcançar o EOF (end of file), desta forma podemos ler um arquivo de duas maneiras:

1 – Redirecionando a entrada do arquivo para o bloco do while assim:

    while read Linha
    do
        echo $Linha
    done < arquivo

2 – Redirecionando a saída de um cat para o while, da seguinte maneira:

    cat arquivo |
    while read Linha
    do
        echo $Linha
    done

Cada um dos processos tem suas vantagens e desvantagens:

Vantagens do primeiro processo:

  • É mais rápido;
  • Não necessita de um subshell para assisti-lo;

Desvantagem do primeiro processo:

  • Em um bloco de instruções grande, o redirecionamento fica pouco visível o que por vezes prejudica a vizualização do código;

Vantagem do segundo processo:

  • Como o nome do arquivo está antes do while, é mais fácil a vizualização do código.

Desvantagens do segundo processo:

  • O Pipe (|) chama um subshell para interpretá-lo, tornando o processo mais lento, pesado e por vezes problemático (veja o exemplo a seguir).

Para ilustrar o que foi dito, veja estes exemplos a seguir:

$ cat readpipe.sh
#!/bin/bash
# readpipe.sh
# Exemplo de read passando arquivo por pipe.

Ultimo=”(vazio)”
cat $0 | # Passando o arq. do script ($0) p/ while
while read Linha
do
Ultimo=”$Linha”
echo “-$Ultimo-”
done
echo “Acabou, Último=:$Ultimo:”

Vamos ver sua execução:

$ readpipe.sh
-#!/bin/bash-
-# readpipe.sh-
-# Exemplo de read passando arquivo por pipe.-

-Ultimo=”(vazio)”-
-cat $0 | # Passando o arq. do script ($0) p/ while-
-while read Linha-
-do-
-Ultimo=”$Linha”-
-echo “-$Ultimo-“-
-done-
-echo “Acabou, Último=:$Ultimo:”-
Acabou, Último=:(vazio):

Como você viu, o script lista todas as suas próprias linhas com um sinal de menos (-) antes e outro depois de cada, e no final exibe o conteúdo da variável $Ultimo. Repare no entanto que o conteúdo desta variável permanece como (vazio).

– Ué será que a variável não foi atualizada?

– Foi, e isso pode ser comprovado porque a linha echo "-$Ultimo-" lista corretamente as linhas.

– Então porque isso aconteceu?

– Por que como eu disse, o bloco de instruções redirecionado pelo pipe (|) é executado em um subshell e lá as variáveis são atualizadas. Quando este subshell termina, as atualizações das variáveis vão para os píncaros do inferno junto com ele. Repare que vou fazer uma pequena mudança nele, passando o arquivo por redirecionamento de entrada (<) e as coisas passarão a funcionar na mais perfeita ordem:

$ cat redirread.sh
#!/bin/bash
# redirread.sh
# Exemplo de read passando arquivo por pipe.

Ultimo=”(vazio)”
while read Linha
do
Ultimo=”$Linha”
echo “-$Ultimo-”
done < $0 # Passando o arq. do script ($0) p/ while
echo “Acabou, Último=:$Ultimo:”

E veja a sua perfeita execução:

$ redirread.sh
-#!/bin/bash-
-# redirread.sh-
-# Exemplo de read passando arquivo por pipe.-

-Ultimo=”(vazio)”-
-while read Linha-
-do-
-Ultimo=”$Linha”-
-echo “-$Ultimo-“-
-done < $0 # Passando o arq. do script ($0) p/ while-
-echo “Acabou, Último=:$Ultimo:”-
Acabou, Último=:echo “Acabou, Último=:$Ultimo:”:

Bem amigos da Rede Shell, para finalizar o comando read só falta mais um pequeno e importante macete que vou mostrar utilizando um exemplo prático. Suponha que você queira listar na tela um arquivo e a cada dez registros esta listagem pararia para que o operador pudesse ler o conteúdo da tela e ela só voltasse a rolar (scroll) após o operador digitar qualquer tecla. Para não gastar papel (da Linux Magazine) pra chuchu, vou fazer esta listagem na horizontal e o meu arquivo (numeros), tem 30 registros somente com números seqüênciais. Veja:

$ seq 30 | xargs -i echo lin {} > numeros # Criando arquivo numeros
$ paste -sd’:’ numeros # Este paste é para exibir o conteúdo sem ser um
#+ por linha, usando dois-pontos como separador
lin 1:lin 2:lin 3:lin 4:lin 5:lin 6:lin 7:lin 8:lin 9:lin 10:lin 11:lin 12:lin 13:lin 14:lin 15:l
in 16:lin 17:lin 18:lin 19:lin 20:lin 21:lin 22:lin 23:lin 24:lin 25:lin 26:lin 27:lin 28:lin 29:
lin 30

$ cat 10porpag.sh
#!/bin/bash
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 1

while read Num
do
let ContLin++ # Contando…
echo -n “$Num ” # -n para nao saltar linha
((ContLin % 10)) > /dev/null || read
done < numeros

Na tentativa de fazer um programa genérico criamos a variável $ContLin (por que na vida real, os registros não são somente números seqüenciais) e parávamos para ler quando o resto da divisão por 10 fosse zero (mandando a saída para /dev/null de forma a não aparecer na tela, sujando-a). Porém, quando fui executar deu a seguinte zebra:

$ 10porpag.sh
lin 1 lin 2 lin 3 lin 4 lin 5 lin 6 lin 7 lin 8 lin 9 lin 10 lin 12 lin 13 lin 14 lin 15 lin 16 l
in 17 lin 18 lin 19 lin 20 lin 21 lin 23 lin 24 lin 25 lin 26 lin 27 lin 28 lin 29 lin 30
$

Repare que faltou a linha lin 11 e a listagem não parou no read. O que houve foi que toda a entrada do loop estava redirecionada do arquivo numeros e desta forma, a leitura foi feita em cima deste arquivo, desta forma perdendo a lin 11 (e também a lin 22 ou qualquer linha múltipla de 11).

Vamos mostrar então como deveria ficar para funcionar a contento:

$ cat 10porpag.sh
#!/bin/bash
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 2

while read Num
do
let ContLin++ # Contando…
echo -n “$Num ” # -n para nao saltar linha
((ContLin % 10)) > /dev/null || read < /dev/tty
done < numeros

Observe que agora a entrada do read foi redirecionada por /dev/tty, que nada mais é senão o terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e não de numeros. É bom realçar que isto não acontece somente quando usamos o redirecionamento de entrada, se houvéssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido.

Veja agora a sua execução:

$ 10porpag.sh
lin 1 lin 2 lin 3 lin 4 lin 5 lin 6 lin 7 lin 8 lin 9 lin 10
lin 11 lin 12 lin 13 lin 14 lin 15 lin 16 lin 17 lin 18 lin 19 lin 20
lin 21 lin 22 lin 23 lin 24 lin 25 lin 26 lin 27 lin 28 lin 29 lin 30

Isto está quase bom mas falta um pouco para ficar excelente. Vamos melhorar um pouco o exemplo para que você o reproduza e teste (mas antes de testar aumente o número de registros de numeros ou reduza o tamanho da tela, para que haja quebra).

$ cat 10porpag.sh
#!/bin/bash
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 3

clear
while read Num
do
((ContLin++)) # Contando…
echo “$Num”
((ContLin % (`tput lines` – 3))) ||
{
read -n1 -p”Tecle Algo ” < /dev/tty # para ler qq caractere
clear # limpa a tela apos leitura
}
done < numeros

A mudança substancial feita neste exemplo é com relação à quebra de página, já que ela é feita a cada quantidade-de-linhas-da-tela (tput lines) menos (-) 3, isto é, se a tela tem 25 linha, listará 22 registros e parará para leitura. No comando read também foi feita uma alteração, inserido um -n1 para ler somente um caractere sem ser necessariamente um <ENTER> e a opção -p para dar a mensagem.

– Bem meu amigo, por hoje é só porque acho que você já está de saco cheio…

– Num tô não, pode continuar…

– Se você não estiver eu estou… Mas já que você está tão empolgado com o Shell, vou te deixar um exercício de apredizagem para você melhorar a sua CDteca que é bastante simples. Reescreva o seu programa que cadastra CDs para montar toda a tela com um único echo e depois vá posicionando à frente de cada campo para receber os valores que serão teclados pelo operador.

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!

Read Full Post »