– 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.
#!/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ó:
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:
#!/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:
$ 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.
#!/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:
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:
$ 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:
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”:
Matricula: 12345
$ echo $Mat
12345
Ou simplificando com a opção -p
:
Matricula: 12345
$ echo $Mat
12345
Para ler uma determinada quantidade de caracteres:
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):
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:
- 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; - A variável
Nom
permaneceu vazia. Ela será valorada somente quando o<ENTER>
for teclado.
Para ler um dado sem ser exibido na tela:
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:
#!/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:
-#!/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:
#!/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:
-#!/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:
$ 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:
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:
#!/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:
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).
#!/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!