– 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:
#!/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:
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:
#!/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:
jneves está logado
$ logado chico
chico não se encontra no pedaç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):
$ 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.
>
) 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:
$ 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:
> 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:
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:
Ou (nesse caso específico) o próprio grep
puro e simples poderia nos quebrar o galho:
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 |
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 == ? ]] && 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:
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:
#!/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? )
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!
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!