Neste artigo são revisados alguns conceitos básicos da codificação de programas, voltados para a criação de scripts shell, e são apresentados alguns conceitos específicos desse ambiente.
Ao programar para um shell POSIX, principalmente os baseados no Bourne Again Shell (bash), encontramos diversas estruturas comuns a outros ambientes e linguagens de programação.
As estruturas de decisão avaliam uma condição e desviam a execução do programa de acordo com o resultado dessa avaliação. O resultado da condição é sempre verdadeiro ou falso.
O if é o comando utilizado para executar um trecho de código apenas quando a <condição> for verdadeira. Opcionalmente, pode ser utilizado o else, cujo código só será executado caso a <condição> seja falsa.
Utiliza-se o comando fi para marcar o fim de um bloco if-then-else.
if <condição>
then
# condição 1 é `verdadeiro`
else # opcional
# condição 1 é `falso`
fi
É possivel aninhar diversos comandos if, e o else encontrado será sempre do último if aberto, ou seja, do último if que não tem um fi correspondente.
if #1
then
if #2
then
# código
else # do if #2
#código
fi # fim do if #2
fi # o if #1 não tem else.
Quando temos um teste logo após o else, ao invés de utilizar else if, o que exigiria utilizar dois fi, podemos associar o elif ao if original.
if <condição 1>
then
# condição 1 é `verdadeiro`
elif <condição 2> # opcional
then
# condição 1 é `falso` e condição 2 é `verdadeiro`
else # opcional
# condição 1 é `falso` e condição 2 é `falso`
fi
Veja um exemplo:
#!/bin/sh
if [ $1 -gt 10 ]
then
echo greater than 10
elif [ $1 -lt 10 ]
then
echo less than 10
else
echo equal to 10
fi
A construção case-in é semelhante ao switch-case em outras linguagens de programação. Um VALOR é comparado a padrões, e se este VALOR for representado pelo padrão em questão, o código associado ao padrão é executado até que se encontre o duplo ponto-e-vírgula (;;).;
Quando associamos o mesmo código a dois ou mais padrões diferentes, separamos os padrões usando o pipe (|). Nesse caso, o código será executado quando VALOR for associado a qualquer um dos padrões.
case <VALOR> in
pattern 1)
# código 1
;;
pattern 2 | pattern 3)
# código 2
;;
*)
# código 3
;;
esac
A avaliação de padrões utilizado é a mesma do shell, por exemplo o padrão [0-9][0-9]* poderia ser utilizado para selecionar qualquer número inteiro, com um ou mais dígitos.
Usualmente, o último padrão do case-in é o *), que aceita “qualquer quantidade de caracteres, independente do caracter”, ou seja, aceita qualquer padrão de VALOR, e funciona como uma opção default.
O próximo exemplo implementa a solução para o mesmo problema apresentado anteriormente, mas utilizando case-in ao invés de if-then-else:
#!/bin/sh
case "$1" in
-[0-9]* | [0-9] ) echo less than 10 ;;
10 ) echo equal to 10 ;;
* ) echo greater than 10 ;;
esac
Podem existir situações onde você deseja continuar a execução de um código de um padrão para o próximo. Nesse caso, você pode utilizar ;& no lugar de ;; para representrar o falltrough no case-in:
#!/bin/sh
case "$1" in
aa* ) echo "Muitos 'a'!" ;&
*) echo "Esse texto sempre aparecerá..." ;;
esac
Utilizamos estruturas de repetição para executar o mesmo bloco de código diversas vezes. O número de vezes que o código será repetido pode depender dos elementos de uma lista, ou da ocorrência de um evento.
A estrutura de repetição for-in executa um bloco de código uma vez para cada um dos elementos de uma lista, e, a cada execução, atribui o próximo elemento da lista a uma variável.
No exemplo, as letras são impressas, uma por linha:
for VAR in a b c d e f g
do
echo "Letra: ${VAR}"
done
Podemos utilizar o comando seq para executar o laço um número específico de vezes:
for INDICE in $(seq 10)
do
if [ $((INDICE % 2)) -eq 0 ]
then
echo "${INDICE} é par."
else
echo "${INDICE} é impar."
fi
done
O comando while executa um bloco de código enquanto uma determinada condição for verdadeira.
while <condição>
do
# condição é verdadeira
done
Por exemplo, podemos executar a leitura de um valor até que o número correto seja inserido:
while [ ${NUM:-0} -ne 10 ]
do
read -p "Menor número positivo de dois digitos? " NUM
done
Note que o bloco de código pode nunca ser executado se a condição for inicialmente falsa, por exemplo while false, ou o bloco nunca terminará se ela for sempre verdadeira (loop infinito), como em while true.
Dentro de uma estrutura de repetição, podemos utilizar os comandos break e continue para executar um “salto” dentro do script.
O comando break encerra a execução do loop, saltando para o comando imediatamente após o done. Já o comando continue, retorna ao início do loop, forçando uma nova iteração (no caso do for-in utilizando um novo elemento da lista).
for INDICE in $(seq 100)
do
if [ $((INDICE % 50 )) -eq 0 ]
then
echo -e "T\n"
break
fi
if [ $((INDICE % 10 )) -eq 0 ]
then
echo -n "X"
continue
fi
echo -n "-"
done
Para o shell um resultado verdadeiro em uma condição do if significa que o programa executado para avaliar a condição terminou sem retornar um código de erro.
O código de erro do último programa executado pelo shell pode ser recuperado utilizando $?. Por exemplo, ao executar o comando true, que sempre retorna verdadeiro, o resultado de echo $? será 0, por outro lado, ao executar o comando false, que sempre retorna falso, o resultado será 1
$ true
$ echo $?
0
$ false
$ echo $?
1
Um comando que permite comparar valores é o comando test (que é equivalente ao comando [, porém o segundo requer que o último parâmetro seja ]). Esse comando pode executar diversas comparações, como verificar se um string é vazia (-z), se um arquivo existe (-f), ou se um número A é maior que um número B (${A} -gt ${B}), entre outros testes.
Por esse motivo, o comando if normalmente é visto como if [ <teste> ].
No entanto, qualquer comando pode ser utilizado como condição para o if. Por exemplo, podemos executar um trecho de código caso uma cópia de arquivo tenha sido executada corretamente:
if cp "${src}" "${dest}"
then
# executa código se a cópia de arquivo for executada
fi
Quando precisamos relacionar duas ou mais condições podemos utilizar, em linguagens de programação, os conectores lógicos AND e OR. Podemos também negar uma condigção com o NOT.
Se temos uma expressão cond1 AND cond2, a expressão só será verdadeira se as duas condições forem verdadeiras. Podemos representar o AND no shell com &&. Por exemplo, true && true é verdadeiro, true && false é falso e [ 10 -gt 2 ] && [-100 -lt 0] é verdadeiro.
No caso de existir uma expressão cond1 OR cond2, a expressão será verdadeira se qualquer uma das duas condições forem verdadeiras. Podemos representar o OR no shell com ||. Por exemplo, true || true é verdadeiro, true || false é verdadeiro, false || true é verdadeiro, e false || false é falso.
Como o shell faz o que chamamos de avaliação de curto circuito, ou seja, ele para de executar a avaliação da expressão quando já sabe o resultado, podemos, em algumas situações, usar a relação de condições para substituir um if. Esse tipo de construção só é recomendado em situações simples, pois pode deixar o código mais difícil de ler:
$ true && false && echo "YES" || echo "NO"
NO
$ true && true && echo "YES" || echo "NO"
YES
$ true && ! false && echo "YES" || echo "NO"
YES
Outra operação importante sobre condições é a negação da condição (NOT), ou seja, o resultado da condição é invertido e um resultado verdadeiro vira falso, e vice-versa. No shell a negação é representada por !.
Podemos utilizar a negação para executar algo apenas quando a condição for falsa, e queremos, por exemplo, evitar a utilização do else:
if ! cp ${src} ${dest}
then
# código executado apenas em caso de falha na cópia.
fi