Papo de Botequim Parte V
- Fala cara! E as idéias estão em ordem? Já fundiu a cuca ou você ainda aguenta mais
Shell?
- Guento! Tô gostando muito! Gostei tanto que até caprichei no exercício que você passou. Lembra que você me pediu para fazer um programa que receberia como parâmetro o nome de um arquivo e que quando executado salvaria este arquivo com o nome original seguido de um til (
~) e colocaria este arquivo dentro do
vi?
- Claro que lembro, me mostre e explique como você fez.
$ cat vira
#!/bin/bash
#
# vira - vi resguardando arquivo anterior
# == = =
# Verificando se foi passado 1 parametro
if [ "$#" -ne 1 ]
then
echo "Erro -> Uso: $0 "
exit 1
fi
Arq=$1
# Caso o arquivo não exista, nao ha copia para ser salva
if [ ! -f "$Arq" ]
then
vi $Arq
exit 0
fi
# Se nao puder alterar o arquivo vou usar o vi para que?
if [ ! -w "$Arq" ]
then
echo "Voce nao tem direito de gravacao em $Arq"
exit 2
fi
# Ja que esta tudo OK, vou salvar a copia e chamar o vi
cp -f $Arq $Arq~
vi $Arq
exit 0
- É, beleza! Mas me diz uma coisa: porque você terminou o programa com um
exit 0?
- Ahhh! Eu descobri que o número após o
exit resultará no código de retorno do programa (o
$?, lembra?), e desta forma, como foi tudo bem sucedido, ele encerraria com o
$? = 0. Porém se você observar, verá que caso o programa não tenha recebido o nome do arquivo ou caso o operador não tivesse direito de gravação sobre este arquivo, o código de retorno (
$?) seria diferente do zero.
- Grande garoto, aprendeu legal, mas é bom deixar claro que
exit 0, simplesmente
exit ou não colocar
exit, produzem igualmente um código de retorno (
$?) igual a zero. Agora vamos falar sobre as instruções de
loop ou laço, mas antes vou passar o conceito de bloco de programa.
Até agora já vimos alguns blocos de programa. Quando te mostrei um exemplo para fazer um
cd para dentro de um diretório que era assim:
cd lmb 2> /dev/null ||
{
mkdir lmb
cd lmb
}
O fragmento contido entre as duas chaves (
{}), forma um bloco de comandos. Também neste exercício que acabamos de ver, em que salvamos o arquivo antes de editá-lo, existem vários blocos de comandos compreendidos entre os
then e os
fi do
if.
Um bloco de comandos também pode estar dentro de um
case, ou entre um
do e um
done.
- Peraí Julio, que
do e
done é esse, não me lembro de você ter falado nisso e olha estou prestando muita atenção...
- Pois é, ainda não havia falado porque não havia chegado o momento propício. Todas as instruções de
loop ou laço, executam os comandos do bloco compreendido entre o
do e o
done.
Comandos de Loop (ou laço)
As instruções de
loop ou laço são o
for, o
while e o
until que passarei a te explicar uma-a-uma a partir de agora.
O comando for
Se você está habituado a programar, certamente já conhece o comando
for, mas o que você não sabe é que o
for, que é uma instrução intrinseca do
Shell (isto significa que o código fonte do comando faz parte do código fonte do
Shell, ou seja em bom programês é um
built-in), é muito mais poderoso que os seus correlatos das outras linguagens.
Vamos entender a sua sintaxe, primeiramente em português e depois como funciona no duro.
para var em val1 val2 ... valn
faça
cmd1
cmd2
cmdn
feito
Onde a variável
var assume cada um dos valores da lista
val1 val2 ... valn e para cada um desses valores executa o bloco de comandos formado por
cmd1,
cmd2 e
cmdn
Agora que já vimos o significado da instrução em português, vejamos a sintaxe correta:
Primeira sintaxe do comando for
for var in val1 val2 ... valn
do
cmd1
cmd2
cmdn
done
Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamos escrever um
script para listar todos os arquivos do nosso diretório separados por dois-pontos, mas primeiro veja:
$ echo *
ArqDoDOS.txt1 confuso incusu logado musexc musicas musinc muslist
Isto é, o
Shell viu o asterisco (
*) expandindo-o com o nome de todos os arquivos do diretório e o comando
echo jogou-os para a tela separados por espaços em branco. Visto isso vamos ver como resolver o problema a que nos propuzemos:
$ cat testefor1
#!/bin/bash
# 1o. Prog didático para entender o for
for Arq in *
do
echo -n $Arq: # A opcao -n eh para nao saltar linha
done
Então vamos executá-lo:
$ testefor1
ArqDoDOS.txt1:confuso:incusu:logado:musexc:musicas:musinc:muslist:$
Como você viu o
Shell transformou o asterísco (que odeia ser chamado de asterístico) em uma lista de arquivos separados por espaços em branco. quando o
for viu aquela lista, ele disse: "Opa, lista separadas por espaços é comigo mesmo!"
O bloco de comandos a ser executado era somente o
echo, que com a opção
-n listou a variável
$Arq seguida de dois-pontos (
:), sem saltar a linha. O cifrão (
$) do final da linha da execução é o
prompt. que permaneceu na mesma linha também em função da opção
-n.
Outro exemplo simples (por enquanto):
$ cat testefor2
#!/bin/bash
# 2o. Prog didático para entender o for
for Palavra in Papo de Botequim
do
echo $Palavra
done
E executando vem:
$ testefor2
Papo
de
Botequim
Como você viu, este exemplo é tão bobo e simples como o anterior, mas serve para mostrar o comportamento básico do
for.
Veja só a força do
for: ainda estamos na primeira sintaxe do comando e já estou mostrando novas formas de usá-lo. Lá atrás eu havia falado que o
for usava listas separadas por espaços em branco, mas isso é uma meia verdade, era só para facilitar a compreensão.
No duro, as listas não são obrigatóriamente separadas por espaços mas antes de prosseguir, deixa eu te mostrar como se comporta uma variável do sistema chamada de
$IFS. Repare seu conteúdo:
$ echo "$IFS" | od -h
0000000 0920 0a0a
0000004
Isto é, mandei a variável (protegida da interpretação do
Shell pelas aspas) para um
dump hexadecimal (
od -h) e resultou:
| Conteúdo da Variável $IFS |
0a |
<ENTER> |
| Hexadecimal |
Significado |
09 |
<TAB> |
20 |
<ESPAÇO> |
Onde o último
0a foi proveniente do
<ENTER> dado ao final do comando. Para melhorar a explicação, vamos ver isso de outra forma:
$ echo ":$IFS:" | cat -vet
: ^I$
:$
Preste atenção na dica a seguir para entender a construção deste comando cat:

No comando
cat, a opção
-e representa o
<ENTER> como um cifrão (
$) e a opção
-t representa o
<TAB> como um
^I. Usei os dois-pontos (
:) para mostrar o início e o fim do
echo. E desta forma, mais uma vez pudemos notar que os três caracteres estão presentes naquela variável.
Agora veja você,
IFS significa
Inter Field Separator ou, traduzindo, separador entre campos. Uma vez entendido isso, eu posso afirmar (porque vou provar) que o comando
for não usa listas separadas por espaços em branco, mas sim pelo conteúdo da variável
$IFS, cujo valor padrão (
default) são esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um
script que recebe o nome do artista como parâmetro e lista as músicas que ele executa, mas primeiramente vamos ver como está o nosso arquivo
musicas:
$ cat musicas
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4
album 3^Artista5~Musica5:Artista6~Musica6
album 4^Artista7~Musica7:Artista1~Musica3
album 5^Artista9~Musica9:Artista10~Musica10
Em cima deste "leiaute" foi desenvolvido o
script a seguir:
$ cat listartista
#!/bin/bash
# Dado um artista, mostra as suas musicas
if [ $# -ne 1 ]
then
echo Voce deveria ter passado um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep $1 && echo $ArtMus | cut -f2 -d~
done
O
script, como sempre, começa testando se os parâmetros foram passados corretamente, em seguida o
IFS foi setado para
<ENTER> e dois-pontos (
:) (como demonstram as aspas em linha diferentes), porque é ele que separa os blocos
Artistan~Musicam. Desta forma, a variável
$ArtMus irá receber cada um destes blocos do arquivo (repare que o
for já recebe os registros sem o álbum em virtude do
cut na sua linha). Caso encontre o parâmetro (
$1) no bloco, o segundo
cut listará somente o nome da música. Vamos executá-lo:
$ listartista Artista1
Artista1~Musica1
Musica1
Artista1~Musica3
Musica3
Artista10~Musica10
Musica10
Êpa! Aconteceram duas coisas indesejáveis: os blocos também foram listados e a
Musica10 idem. Além do mais, o nosso arquivo de músicas está muito simples, na vida real, tanto a música quanto o artista têm mais de um nome. Suponha que o artista fosse uma dupla sertaneja chamada
Perereca & Peteleca (não gosto nem de dar a idéia com receio que isso se torne realidade:). Neste caso o
$1 seria Perereca e o resto deste lindo nome seria ignorado na pesquisa.
Para que isso não ocorresse, eu deveia passar o nome do artista entre aspas (
") ou alterar
$1 por
$@ (que significa todos os parâmetros passados), que é a melhor solução, mas neste caso eu teria que modificar a crítica dos parâmetros e o
grep. A nova crítica não seria se eu passei um parâmetro, mas
pelo menos um parâmetro e quanto ao
grep, veja só o que resultaria após a substituição do
$* (que entraria no lugar do
$1) pelos parâmetros:
echo "$ArtMus" | grep perereca & peteleca
O que resultaria em erro. O correto seria:
echo "$ArtMus" | grep -i "perereca & peteleca"
Onde foi colocado a opção
-i para que a pesquisa ignorasse maiúsculas e minúsculas e as aspas também foram inseridas para que o nome do artista fosse visto como uma só cadeia monolítica.
Ainda falta consertar o erro dele ter listado o
Artista10. Para isso o melhor é dizer ao
grep que a cadeia está no início de
$ArtMus (a expressão regular para dizer que está no início é
^) e logo após vem um til (
~). É necessário também que se redirecione a saída do grep para
/dev/null para que os blocos não sejam mais listados. Veja então a nova (e definitiva) cara do programa:
$ cat listartista
#!/bin/bash
# Dado um artista, mostra as suas musicas
# versao 2
if [ $# -eq 0 ]
then
echo Voce deveria ter passado pelo menos um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d~
done
Que executando vem:
$ listartista Artista1
Musica1
Musica3
Segunda sintaxe do comando for
for var
do
cmd1
cmd2
cmdn
done
- Ué, sem o
in como ele vai saber que valor assumir?
- Pois é, né? Esta construção a primeira vista parece xquisita mas é bastante simples. Neste caso,
var assumirá um-a-um cada um dos parâmetros passados para o progama.
Vamos logo aos exemplos para entender melhor. Vamos fazer um
script que receba como parâmetro um monte de músicas e liste seus autores:
$ cat listamusica
#!/bin/bash
# Recebe parte dos nomes de musicas como parametro e
# lista os interpretes. Se o nome for composto, deve
# ser passado entre aspas.
# ex. "Eu nao sou cachorro nao" "Churrasquinho de Mae"
#
if [ $# -eq 0 ]
then
echo Uso: $0 musica1 [musica2] ... [musican]
exit 1
fi
IFS="
:"
for Musica
do
echo $Musica
Str=$(grep -i "$Musica" musicas) ||
{
echo " Não encontrada"
continue
}
for ArtMus in $(echo "$Str" | cut -f2 -d^)
do
echo " $ArtMus" | grep -i "$Musica" | cut -f1 -d~
done
done
Da mesma forma que os outros, começamos o exercício com uma crítica sobre os parâmetros recebidos, em seguida fizemos um
for em que a variável
$Musica receberá cada um dos parâmetros passados, colocando em
$Str todos os álbuns que contém as músicas passadas. Em seguida, o outro
for pega cada bloco
Artista~Musica nos registros que estão em
$Str e lista cada artista que execute aquela música.
Como sempre vamos executá-lo para ver se funciona mesmo:
$ listamusica musica3 Musica4 "Eguinha Pocotó"
musica3
Artista3
Artista1
Musica4
Artista4
Eguinha Pocotó
Não encontrada
A listagem ficou feinha porque ainda não sabemos formatar a saída, mas qualquer dia desses, quando você souber posicionar o cursor, fazer negrito, trabalhar com cores e etc, faremos esta listagem novamente usando todas estas perfumarias e ela ficará muito
fashion.
A esta altura dos acontecimentos você deve estar se perguntando: "E aquele
for tradicional das outras linguagens em que ele sai contando a partir de um número, com um determinado incremento até alcançar uma condição?"
E é aí que eu te respondo: "Eu não te disse que o nosso
for é mais porreta que os outros?" Para fazer isso existem duas formas:
1 - Com a primeira sintaxe que vimos, como nos exemplos a seguir direto no
prompt:
$ for i in $(seq 9)
> do
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Neste a variável
i assumiu os inteiros de 1 a 9 gerados pelo comando
seq e a opção
-n do
echo foi usada para não saltar linha a cada número listado (sinto-me ecologicamente correto por não gastar um monte de papel da revista quando isso pode ser evitado). Ainda usando o
for com
seq:
$ for i in $(seq 3 9)
> do
> echo -n "$i "
> done
4 5 6 7 8 9
Ou ainda na forma mais completa do
seq:
$ for i in $(seq 0 3 9)
> do
> echo -n "$i "
> done
0 3 6 9
2 – A outra forma de fazer o desejado é com uma sintaxe muito semelhante ao
for da linguagem C, como veremos a seguir.
Terceira sintaxe do comando for
for ((var=ini; cond; incr))
do
cmd1
cmd2
cmdn
done
Onde:
var=ini - Significa que a variável
var começará de um valor inicial
ini;
cond - Siginifica que o
loop ou laço do
for será executado enquanto
var não atingir a condição
cond;
incr - Significa o incremento que a variável
var sofrerá em cada passada do
loop.
Como sempre vamos aos exemplos que a coisa fica mais fácil:
$ for ((i=1; i<=9; i++))
> do
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Neste caso a variável
i partiu do valor inicial
1, o bloco de comando (neste caso somente o
echo) será executado enquanto
i menor ou igual (
<=) a
9 e o incremento de
i será de
1 a cada passada do
loop.
Repare que no
for propriamente dito (e não no bloco de comandos) não coloquei um cifrão (
$) antes do
i, e a notação para incrementar (
i++) é diferente do que vimos até agora. Isto é porque o uso de parênteses duplos (assim como o comando
let) chama o interpretador aritmético do
Shell, que é mais tolerante.
Como me referi ao comando
let, só para mostrar como ele funciona e a versatilidade do
for, vamos fazer a mesma coisa, porém omitindo a última parte do escopo do
for, passando-a para o bloco de comandos.
$ for ((; i<=9;))
> do
> let i++
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Repare que o incremento saiu do corpo do
for e passou para o bloco de comandos, repare também que quando usei o
let, não foi necessário sequer inicializar a variável
$i. Veja só os comandos a seguir dados diretamente no
prompt para mostrar o que acabo de falar:
$ echo $j
$ let j++
$ echo $j
1
Ou seja, a variável
$j sequer existia e no primeiro
let assumiu o valor
0 (zero) para, após o incremento, ter o valor
1.
Veja só como as coisas ficam simples:
$ for arq in *
> do
> let i++
> echo "$i -> $Arq"
> done
1 -> ArqDoDOS.txt1
2 -> confuso
3 -> incusu
4 -> listamusica
5 -> listartista
6 -> logado
7 -> musexc
8 -> musicas
9 -> musinc
10 -> muslist
11 -> testefor1
12 -> testefor2
- Pois é amigo, tenho certeza que você já tomou um xarope do comando
for. Por hoje chega, na próxima vez que nos encontrarmos falaremos sobre outras instruções de
loop, mas eu gostaria que até lá você fizesse um pequeno
script para contar a quantidade de palavras de um arquivo texto, cujo nome seria recebido por parâmetro.
OBS: Essa contagem tem de ser feita usando o comando for para se habituar ao seu uso. Não vale usar o
wc -w.
- Aê Chico! Traz a saideira.
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!

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki-SL?
Send feedback