Já vimos como escrever comandos simples, em que a um comando se seguem argumentos. Agora vamos ver como construir comandos mais complexos compondo comandos simples.
Antes de começarmos, uma breve introdução à entrada/saída.
Os processos têm "canais de E/S padrão".
O comando tr é um exemplo muito puro disto.
Tudo o que faz é ler texto da sua entrada, efetuar transliterações de carateres e imprimir o texto resultante na sua saída.
Veremos mais sobre a manipulação dos canais "stdio" mais adiante no programa.
Esta é uma das grandes armas da programação em shell. As pipelines permitem-te criar transformações sofisticadas sobre um fluxo de texto.
Para produzir uma lista ordenada de utilizadores:
cat /etc/passwd | cut -d : -f 1 | sort
O símbolo de pipe (|) liga a saída de um comando à entrada de outro.
O cut lê a saída do cat e o sort lê a saída do cut.
Por predefinição, cada comando de um pipeline é executado numa subshell separada. (Uma subshell é um processo filho que é uma cópia da shell atualmente em execução.)
Executar pipelines tem um custo de desempenho.
Se te vires com pipelines longos de comandos semelhantes, considera combiná-los num único comando.
Por exemplo, pipelines que usam várias instâncias de grep, cut, sed, awk e tr podem geralmente ser combinados num único comando awk, por uma questão de eficiência.
O estado de saída de um pipeline é o estado de saída do último comando do pipeline. Isto significa que, se algum comando anterior falhou mas o último comando teve sucesso, o estado de saída de todo o pipeline é zero.
Há uma opção da shell que pode alterar isto;
se a opção "pipefail" estiver ativada (com set -o pipefail), o estado de saída do pipeline será:
Uma lista de comandos é uma sequência de pipelines separadas por ; (ou uma nova linha), && ou ||.
A; B é uma lista de comandos em que B é executado depois de A terminar.A && B, em que B só é executado se A tiver sucesso (sair com estado zero).A || B, em que B só é executado se A falhar (sair com um estado diferente de zero).O estado de saída de uma lista de comandos é o estado de saída do último comando executado.
Os operadores && e || podem ser encadeados, de modo que o comando seguinte é executado condicionalmente com base no estado dos comandos anteriores.
Por exemplo
A && B && C && D || E
Usa estes operadores lógicos com moderação. Podem rapidamente dar origem a código ilegível ou a uma lógica difícil de compreender.
Por exemplo, podes pensar que estes dois são iguais:
if A; then B; else C; fi
A && B || C
A diferença está em saber quando é que o C é executado?
Aqui ficam alguns exemplos em que as listas de comandos podem simplificar o código.
Imagina que tens um ficheiro de dados com informação sobre triângulos, em que cada triângulo é representado por três linhas separadas que contêm os lados do triângulo.
$ cat triangle.dat
3
4
5
9
12
14
Podes usar um ciclo while em que a condição são três comandos read separados:
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
Partindo do princípio de que is_pythagorean é um comando que determina se os três lados satisfazem a equação de Pitágoras, a saída seria:
3:4:5 is pythagorean
9:12:14 is not pythagorean
Muitas linguagens de programação têm uma forma de asserção em que é lançada uma exceção se uma condição falhar
assert(user == "Administrator", "wrong user");
Podemos usar um operador OR no Bash para simular essa função:
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
As listas de comandos longas tornam-se difíceis de ler muito depressa. Usar novas linhas com liberalidade pode ajudar bastante.
Considera este exemplo, em que uma palavra é adicionada a um array se duas condições se verificarem.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
O Bash permite-te acrescentar uma nova linha depois de um pipe ou de um operador lógico.
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
No entanto, o operador pode passar facilmente despercebido no fim da linha. Usar uma continuação de linha permite-te colocar o operador primeiro, o que torna mais óbvio que a lista continua:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Uma continuação de linha é a sequência de dois carateres "barra invertida" e "nova linha" (\ + \n).
Quando o Bash vê essa sequência, esta é simplesmente removida do código, continuando assim a linha atual com a linha seguinte.
Tem cuidado para não deixar espaços entre a barra invertida e a nova linha.
Aqui está outro exemplo
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
ou
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort