– 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.
#!/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:
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:
#!/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:
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):
#!/bin/bash
# 2o. Prog didático para entender o for
for Palavra in Papo de Botequim
do
echo $Palavra
done
E executando vem:
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:
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 | |
---|---|
Hexadecimal | Significado |
09 |
<TAB> |
20 |
<ESPAÇO> |
0a |
<ENTER> |
Onde o último 0a
foi proveniente do <ENTER>
dado ao final do comando. Para melhorar a explicação, vamos ver isso de outra forma:
: ^I$
:$
Preste atenção na dica a seguir para entender a construção deste comando cat:
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
:
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:
#!/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:
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:
#!/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:
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:
#!/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:
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:
> 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
:
> do
> echo -n “$i ”
> done
4 5 6 7 8 9
Ou ainda na forma mais completa do seq
:
> 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:
> 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.
> 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
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:
> 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!