Você está na página 1de 5

Linux User Papo de botequim

Papo de Botequim
Curso de Shell Script

Parte VII

Dave Hamilton - www.sxc.hu


De pouco adianta ter acesso à informação se ela não puder ser apresentada
de forma atraente e que facilite a compreensão. O comando tput pode ser
usado por shell scripts para posicionar caracteres e criar todos os tipos de
efeito com o texto mostrado na tela. Garçom, solta uma geladinha!
por Julio Cezar Neves

C
umequié, rapaz! Derreteu os pen- – Peraí, deixa eu ver se entendi o que você – É, tô vendo que o bichinho do shell te
samentos para fazer o scriptzinho fez: você coloca na variável Dir a última pegou. Vamos ver como ler dados, mas
que eu te pedi? linha do arquivo a ser restaurado, em antes vou te mostrar um comando que
– É, eu realmente tive de colocar muita nosso caso /tmp/$LOGNAME/$1 (onde te dá todas as ferramentas para formatar
pensação na tela preta, mas acho que $LOGNAME é o nome do usuário logado, uma tela de entrada de dados.
fi nalmente consegui! Bem, pelo menos e $1 é o primeiro parâmetro que você
nos testes que fi z a coisa funcionou, passou ao script), já que foi lá que arma- O comando tput
mas você tem sempre que botar chifres zenamos o nome e caminho originais do O principal uso desse comando é o posi-
em cabeça de cachorro! arquivo antes de movê-lo para o diretório cionamento do cursor na tela. Alguns
– Não é bem assim. É que programar em (defi nido na variável Dir). O comando parâmetros podem não funcionar se o
Shell Script é muito fácil, mas o que é grep -v apaga essa linha, restaurando modelo de terminal defi nido pela vari-
realmente importante são as dicas e o arquivo ao estado original, e o manda ável de ambiente $TERM não suportá-los.
macetes que não são triviais. As cor- de volta pra onde ele veio. A última linha A tabela 1 apresenta apenas os principais
reções que faço são justamente para o apaga da “lixeira”. Sensacional! Impe- parâmetros e os efeitos resultantes, mas
mostrá-los. Mas vamos pedir dois cho- cável! Nenhum erro! Viu? Você já está existem muito mais deles. Para saber tudo
pes enquanto dou uma olhadela no teu pegando as manhas do shell! sobre o tput, veja a referência [1].
script lá na listagem 1. Aê Chico, traz – Então vamos lá, chega de lesco-lesco Vamos fazer um programa bem besta
dois chopes! E não se esqueça que um e blá-blá-blá, sobre o quê nós vamos e fácil para ilustrar melhor o uso desse
deles é sem colarinho! falar hoje? comando. É uma versão do famigerado
“Alô Mundo”, só que dessa vez a frase
Listagem 1 – restaura.sh será escrita no centro da tela e em vídeo
reverso. Depois disso, o cursor voltará para
01 #!/bin/bash
a posição original. Veja a listagem 2.
02 #
Como o programa já está todo comen-
03 # Restaura arquivos deletados via erreeme
tado, acho que a única linha que precisa de
04 #
explicação é a 8, onde criamos a variável
05
Coluna. O estranho ali é aquele número
06 if [ $# -eq 0 ]
9, que na verdade indica o tamanho da
07 then
cadeia de caracteres que vou escrever na
08 echo "Uso: $0 <Nome do arquivo a ser restaurado>"
tela. Dessa forma, este programa somente
09 exit 1
conseguiria centralizar cadeias de 9 carac-
10 fi
teres, mas veja isto:
11 # Pega nome do arquivo/diretório original na última linha
12 Dir='tail -1 /tmp/$LOGNAME/$1'
$ var=Papo
13 # O grep -v exclui a última linha e recria o arquivo com o diretório
$ echo ${#var}
14 # e nome originalmente usados
4
15 grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1
$ var="Papo de Botequim"
16 # Remove o arquivo que já estava moribundo
$ echo ${#var}
17 rm /tmp/$LOGNAME/$1
16

86 abril 2005 edição 07


www.linuxmagazine.com.br
Linux User Papo de botequim

Tabela 1: Parâmetros do tput e a linha 12 (echo $1) passaria a ser:

Parâmetro Efeito echo $*


cup lin col CUrsor Position – Posiciona o cursor na linha lin e coluna col. A origem (0,0) fica no
canto superior esquerdo da tela. Lendo dados da tela
bold Coloca a tela em modo negrito Bem, a partir de agora vamos aprender
rev Coloca a tela em modo de vídeo reverso tudo sobre leitura. Só não posso ensinar
a ler cartas e búzios porque se soubesse
smso Idêntico ao anterior
estaria rico, num pub Londrino tomando
smul Sublinha os caracteres um scotch e não em um boteco tomando
blink Deixa os caracteres piscando chope. Mas vamos em frente.
sgr0 Restaura a tela a seu modo normal Da última vez em que nos encontramos
reset Limpa o terminal e restaura suas definições de acordo com terminfo, ou seja, o ter- eu dei uma palhinha sobre o comando
minal volta ao comportamento padrão definido pela variável de ambiente $TERM read. Antes de entrarmos em detalhes,
veja só isso:
lines Informa a quantidade de linhas que compõem a tela
cols Informa a quantidade de colunas que compõem a tela $ read var1 var2 var3
el Erase Line – Apaga a linha a partir da posição do cursor Papo de Botequim
ed Erase Display – Apaga a tela a partir da posição do cursor $ echo $var1
Papo
il n Insert Lines – Insere n linhas a partir da posição do cursor
$ echo $var2
dl n Delete Lines – Remove n linhas a partir da posição do cursor
de
dch n Delete CHaracters – Apaga n caracteres a partir da posição do cursor $ echo $var3
sc Save Cursor position – Salva a posição do cursor Botequim
rc Restore Cursor position – Coloca o cursor na posição marcada pelo último sc $ read var1 var2
Papo de Botequim
$ echo $var1
Ahhh, melhorou! Então agora sabemos essa construção devolve o número de Papo
que a construção ${#variavel} devolve caracteres do primeiro parâmetro pas- $ echo $var2
a quantidade de caracteres da variável. sado para o programa. Se o parâmetro de Botequim
Assim sendo, vamos otimizar o nosso tivesse espaços em branco, seria preciso
programa para que ele escreva em vídeo colocá-lo entre aspas, senão o $1 leva- Como você viu, o read recebe uma
reverso, no centro da tela (e indepen- ria em conta somente o pedaço antes lista de parâmetros separada por espa-
dente do número de caracteres) a cadeia do primeiro espaço. Para evitar este ços em branco e coloca cada item dessa
de caracteres passada como parâmetro e aborrecimento, é só substituir o $1 por lista em uma variável. Se a quantidade
depois retorne o cursor à posição em que $*, que como sabemos é o conjunto de de variáveis for menor que a quantidade
estava antes da execução do script. Veja todos os parâmetros. Então a linha 8 de itens, a última variável recebe o res-
o resultado na listagem 3. ficaria assim: tante deles. Eu disse lista separada por
Este script é igual ao anterior, só que espaços em branco, mas agora que você
trocamos o valor fixo na variável Coluna # Centralizando a mensagem na tela já conhece tudo sobre o $IFS (Inter Field
(9) por ${#1}, onde esse 1 é $1, ou seja, Coluna=`$(((Colunas - ${#*}) / 2))` Separator – Separador entre campos), que

Listagem 2: alo.sh Listagem 3: alo.sh melhorado


01 #!/bin/bash 01 #!/bin/bash
02 # Script bobo para testar 02 # Script bobo para testar
03 # o comando tput (versao 1) 03 # o comando tput (versão 2.0)
04 04
05 Colunas=`tput cols` # Salva a quantidade de colunas na tela 05 Colunas=`tput cols` # Salva a quantidade de colunas na tela
06 Linhas=`tput lines` # Salva a quantidade linhas na tela 06 Linhas=`tput lines` # Salva a quantidade de linhas na tela
07 Linha=$((Linhas / 2)) # Qual é a linha central da tela? 07 Linha=$((Linhas / 2)) # Qual é a linha central da tela?
08 Coluna=$(((Colunas - 9) / 2)) # Centraliza a mensagem na tela 08 Coluna=$(((Colunas - ${#1}) / 2)) # Centraliza a mensagem na tela
09 tput sc # Salva a posição do cursor 09 tput sc # Salva a posicao do cursor
10 tput cup $Linha $Coluna # Posiciona o cursor antes de escrever 10 tput cup $Linha $Coluna # Posiciona o cursor antes de escrever
11 tput rev # Vídeo reverso 11 tput rev # Video reverso
12 echo Alô Mundo 12 echo $1
13 tput sgr0 # Restaura o vídeo ao normal 13 tput sgr0 # Restaura o vídeo ao normal
14 tput rc # Restaura o cursor à posição original 14 tput rc # Devolve o cursor à posição original

88 abril 2005 edição 07


www.linuxmagazine.com.br
Papo de botequim Linux User

eu te apresentei quando falávamos do para que o \n fosse entendido como $ read -t2 -p "Digite seu nome completo: U
comando for, será que ainda acredita uma quebra de linha (new line) e não " Nom || echo 'Eita moleza!'
nisso? Vamos testar: como um literal. Sob o Bash exis- Digite seu nome completo: Eita moleza!
tem diversas opções do read que ser- $ echo $Nom
$ oIFS="$IFS" vem para facilitar a sua vida. Veja
$ IFS=: a tabela 2. O exemplo acima foi uma brincadeira,
$ read var1 var2 var3 E agora direto aos exemplos curtos pois eu só tinha 2 segundos para digitar o
Papo de Botequim para demonstrar estas opções. Para ler meu nome completo e mal tive tempo de
$ echo $var1 um campo “Matrícula”: teclar um J (aquele colado no Eita), mas
Papo de Botequim ele serviu para mostrar duas coisas:
$ echo $var2 # -n não salta linha P 1) O comando após o par de barras verti-
$ echo $var3 $ echo -n "Matricula: "; read Mat cais (o ou – or – lógico, lembra-se?) será
$ read var1 var2 var3 Matricula: 12345 executado caso a digitação não tenha
Papo:de:Botequim $ echo $Mat sido concluída no tempo estipulado;
$ echo $var1 12345 P 2) A variável Nom permaneceu vazia. Ela
Papo só receberá um valor quando o ENTER
$ echo $var2 Podemos simplificar as coisas usando for teclado.
de a opção -p:
$ echo $var3 $ read -sp “Senha: “
Botequim $ read -p "Matricula: " Mat Senha: $ echo $REPLY
$ IFS="$oIFS" Matricula: 12345 segredo :)
$ echo $Mat
Viu? eu estava furado! O read lê uma 12345 Aproveitei um erro no exemplo ante-
lista, assim como o for, separada pelos rior para mostrar um macete. Quando
caracteres da variável $IFS. Veja como E podemos ler apenas uma quantidade escrevi a primeira linha, esqueci de colo-
isso pode facilitar a sua vida: pré-determinada de caracteres: car o nome da variável que iria receber a
senha e só notei isso quando ia escrevê-
$ grep julio /etc/passwd $ read -n5 -p"CEP: " Num ; read -n3 U la. Felizmente a variável $REPLY do Bash
julio:x:500:544:Julio C. Neves - 7070:U -p- Compl contém a última seqüência de caracteres
/home/julio:/bin/bash CEP: 12345-678$ digitada – e me aproveitei disso para não
$ oIFS="$IFS" # Salva o IFS antigo. $ echo $Num perder a viagem. Teste você mesmo o que
$ IFS=: 12345 acabei de fazer.
$ grep julio /etc/passwd | read lname U $ echo $Compl O exemplo que dei, na verdade, era para
lixo uid gid coment home shell 678 mostrar que a opção -s impede que o que
$ echo -e "$lname\n$uid\n$gid\n$comentU está sendo digitado seja mostrado na tela.
\n$home\n$shell" No exemplo acima executamos duas Como no exemplo anterior, a falta do new
julio vezes o comando read: um para a pri- line fez com que o prompt de comando
500 meira parte do CEP e outra para o seu ($) permanecesse na mesma linha.
544 complemento, deste modo formatando Agora que sabemos ler da tela, vejamos
Julio C. Neves - 7070 a entrada de dados. O cifrão ($) logo como se lêem os dados dos arquivos.
/home/julio após o último algarismo digitado é
/bin/bash necessário porque o read não inclui Lendo arquivos
$ IFS="$oIFS" # Restaura o IFS por padrão um caractere new line Como eu já havia lhe dito, e você deve se
implícito, como o echo. lembrar, o while testa um comando e exe-
Como você viu, a saída do grep foi Para ler só durante um determinado cuta um bloco de instruções enquanto esse
redirecionada para o comando read, limite de tempo (também conhecido comando for bem sucedido. Ora, quando
que leu todos os campos de uma só como time out): você está lendo um arquivo para o qual
tacada. A opção -e do echo foi usada você tem permissão de leitura, o read só
será mal sucedido quando alcançar o EOF
Tabela 2: Opções do read (End Of File – Fim do Arquivo). Portanto,
podemos ler um arquivo de duas maneiras.
Opção Ação A primeira é redirecionando a entrada do
-p prompt Escreve “prompt” na tela antes de fazer a leitura arquivo para o bloco while, assim:
-n num Lê até num caracteres
while read Linha
-t seg Espera seg segundos para que a leitura seja concluída do
-s Não exibe na tela os caracteres digitados. echo $Linha
done < arquivo

abril 2005 edição 07 89


www.linuxmagazine.com.br
Linux User Papo de botequim

A segunda é redirecionando a saída Como você viu, o script lista suas pró- que vou mostrar com um exemplo prático.
de um cat para o while, da seguinte prias linhas com um sinal de menos (-) Suponha que você queira listar um arquivo
maneira: antes e outro depois de cada uma e, no e quer que a cada dez registros essa listagem
final, exibe o conteúdo da variável $Ultimo. pare para que o operador possa ler o con-
cat arquivo | Repare, no entanto, que o conteúdo dessa teúdo da tela, e que ela só continue depois
while read Linha variável permanece vazio. Ué, será que de o operador pressionar qualquer tecla.
do a variável não foi atualizada? Foi, e isso Para não gastar papel (da Linux Magazine),
echo $Linha pode ser comprovado porque a linha echo vou fazer essa listagem na horizontal. Meu
done "-$Ultimo-" lista corretamente as linhas. arquivo (numeros) tem 30 registros com
Então por que isso aconteceu? números seqüenciais. Veja:
Cada um dos processos tem suas van- Como eu disse, o bloco de instruções
tagens e desvantagens. O primeiro é mais redirecionado pelo pipe (|) é executado $ seq 30 > numeros
rápido e não necessita de um subshell em um subshell e, lá, as variáveis são $ cat 10porpag.sh
para assisti-lo mas, em contrapartida, o atualizadas. Quando esse subshell ter- #!/bin/bash
redirecionamento fica pouco visível em mina, as atualizações das variáveis vão # Programa de teste para escrever
um bloco de instruções grande, o que para as profundezas do inferno junto com # 10 linhas e parar para ler
por vezes prejudica a visualização do ele. Repare que vou fazer uma pequena # Versão 1
código. O segundo processo traz a van- mudança no script, passando o arquivo while read Num
tagem de que, como o nome do arquivo por redirecionamento de entrada (<), e do
está antes do while, a visualização do as coisas passarão a funcionar na mais let ContLin++ # Contando...
código é mais fácil. Entretanto, o Pipe perfeita ordem: # -n para não saltar linha
(|) chama um subshell para interpretá-lo, echo -n "$Num "
tornando o processo mais lento e pesado. $ cat redirread.sh ((ContLin % 10)) > /dev/null || read
Para ilustrar o que foi dito, veja os exem- #!/bin/bash done < numeros
plos a seguir: # redirread.sh
# Exemplo de read passando o arquivo Na tentativa de fazer um programa
$ cat readpipe.sh # por um pipe. genérico criamos a variável $ContLin (na
#!/bin/bash Ultimo="(vazio)" vida real, os registros não são somente
# readpipe.sh # Passa o script ($0) para o while números seqüenciais) e, quando testamos
# Exemplo de read passando um arquivo while read Linha se o resto da divisão era zero, mandamos
# por um pipe. do a saída para /dev/null, pra que ela não
Ultimo="(vazio)" Ultimo="$Linha" apareça na tela. Mas quando fui executar
# Passa o script ($0) para o while echo "-$Ultimo-" o programa descobri a seguinte zebra:
cat $0 | while read Linha done < $0
do echo "Acabou, Último=:$Ultimo:" $ 10porpag.sh
Ultimo="$Linha" 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 U
echo "-$Ultimo-" Veja como ele roda perfeitamente: 18 19 20 21 23 24 25 26 27 28 29 30
done
echo "Acabou, Último=:$Ultimo:" $ redirread.sh Repare que faltou o número 11 e a lista-
-#!/bin/bash- gem não parou no read. Toda a entrada do
Vamos ver o resultado de sua execução: -# redirread.sh- loop estava redirecionada para o arquivo
-# Exemplo de read passando o arquivo numeros e a leitura foi feita em cima desse
$ readpipe.sh -# por um pipe.- arquivo, perdendo o número 11. Vamos
-#!/bin/bash- -- mostrar como deveria ficar o código para
-# readpipe.sh- -Ultimo="(vazio)"- que ele passe a funcionar a contento:
-# Exemplo de read passando um arquivo -while read Linha-
-# por um pipe.- -do- $ cat 10porpag.sh
-- -Ultimo="$Linha"- #!/bin/bash
-Ultimo="(vazio)"- -echo "-$Ultimo-"- # Programa de teste para escrever
-# Passa o script ($0) para o while- -# Passa o script ($0) para o while- # 10 linhas e parar para ler - Versão 2
-cat $0 | - -done < $0- while read Num
-while read Linha- -echo "Acabou, Último=:$Ultimo:"- do
-do- Acabou, Último=:echo "Acabou,U let ContLin++ # Contando...
-Ultimo="$Linha"- Último=:$Ultimo:": # -n para não saltar linha
-echo "-$Ultimo-"- echo -n "$Num "
-done- Bem, amigos da Rede Shell, para finali- ((ContLin % 10)) > /dev/null || read U
-echo "Acabou, Último=:$Ultimo:"- zar a aula sobre o comando read só falta < /dev/tty
Acabou, Último=:(vazio): mais um pequeno e importante macete done < numeros

90 abril 2005 edição 07


www.linuxmagazine.com.br
Papo de botequim Linux User

Repare que agora a entrada do read foi # 10 linhas e parar para ler – Bem meu amigo, por hoje é só porque
redirecionada para /dev/tty, que nada # Versão 3 acho que você já está de saco cheio…
mais é senão o terminal corrente, expli- clear – Num tô não, pode continuar…
citando desta forma que aquela leitura while read Num – Se você não estiver eu estou… Mas já
seria feita do teclado e não do arquivo do que você está tão empolgado com o shell,
numeros. É bom realçar que isso não # Contando... vou te deixar um serviço bastante sim-
acontece somente quando usamos o redi- ((ContLin++)) ples para você melhorar a sua cdteca:
recionamento de entrada; se tivéssemos echo "$Num" Monte toda a tela com um único echo
usado o redirecionamento via pipe (|), o ((ContLin % (`tput lines` - 3))) || e depois posicione o cursor à frente de
mesmo teria ocorrido. Veja agora a exe- { cada campo para receber o valor que
cução do script: # para ler qualquer caractere será digitado pelo operador.
read -n1 -p"Tecle Algo " < /dev/tty Não se esqueçam que, em caso de
$ 10porpag.sh # limpa a tela após a leitura qualquer dúvida ou falta de companhia
1 2 3 4 5 6 7 8 9 10 clear para um chope é só mandar um e-mail
11 12 13 14 15 16 17 18 19 20 } para julio.neves@gmail.com. Vou aproveitar
21 22 23 24 25 26 27 28 29 30 done < numeros também para fazer uma propaganda:
digam aos amigos que quem estiver a
Isso está quase bom, mas ainda falta A mudança substancial feita neste exem- fim de fazer um curso “porreta” de pro-
um pouco para ficar excelente. Vamos plo é com relação à quebra de página, já gramação em shell deve mandar um e-
melhorar o exemplo para que você o que ela é feita a cada quantidade-de-linhas- mail para julio.neves@tecnohall.com.br para
reproduza e teste (mas, antes de testar, da-tela (tput lines) menos (-) três, isto informar-se. Até mais!
aumente o número de registros em nume- é, se a tela tem 25 linhas, o programa
ros ou reduza o tamanho da tela, para listará 22 registros e parará para leitura. Informações
que haja quebra de linha). No comando read também foi feita uma [1] Página oficial do Tput: http://www.cs.utah.edu/
alteração, inserido o parâmetro -n1 para dept/old/texinfo/tput/tput.html#SEC4
$ cat 10porpag.sh ler somente um caractere qualquer, não
[2] Página oficial do Bash: http://www.gnu.org/
#!/bin/bash necessariamente um ENTER, e a opção software/bash/bash.html
# Programa de teste para escrever -p para exibir uma mensagem.

abril 2005 edição 07 91


www.linuxmagazine.com.br

Você também pode gostar