Já vimos como escrever comandos simples, em que um comando é seguido de argumentos. Agora vamos ver como montar comandos mais complexos compondo comandos simples.
Antes de começar, uma rápida introdução à entrada e à saída.
Os processos têm "canais padrão de entrada e saída".
O comando tr é um exemplo bem puro disso.
Tudo o que ele faz é ler texto da sua entrada, fazer transliterações de caracteres e imprimir o texto resultante na sua saída.
Vamos ver mais sobre como manipular os canais de "stdio" mais adiante, no programa.
Este é um dos recursos mais poderosos da programação de shell. Os pipelines permitem criar transformações sofisticadas em um fluxo de texto.
Para produzir uma lista ordenada de usuários:
cat /etc/passwd | cut -d : -f 1 | sort
O símbolo de pipe (|) conecta 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 padrão, cada comando de um pipeline roda em um subshell separado. (Um subshell é um processo filho que é uma cópia do shell em execução no momento.)
Existe um custo de desempenho ao rodar pipelines.
Se você perceber que tem pipelines longos de comandos semelhantes, considere combiná-los em um único comando.
Por exemplo, pipelines que usam várias instâncias de grep, cut, sed, awk e tr geralmente podem ser combinados em um único comando awk para ganhar eficiência.
O status de saída de um pipeline é o status de saída do último comando do pipeline. Isso significa que, se algum comando anterior falhou mas o último comando teve êxito, o status de saída do pipeline inteiro é zero.
Existe uma configuração do shell que pode mudar isso;
se a configuração "pipefail" estiver ativada (com set -o pipefail), o status de saída do pipeline será:
Uma lista de comandos é uma sequência de pipelines separados por ; (ou nova linha), && ou ||.
A; B é uma lista de comandos em que B é executado depois que A termina.A && B, em que B só é executado se A tiver êxito (sair com status zero).A || B, em que B só é executado se A falhar (sair com status diferente de zero).O status de saída de uma lista de comandos é o status de saída do último comando executado.
Os operadores && e || podem ser encadeados de modo que o próximo comando seja executado condicionalmente, com base no status dos comandos anteriores.
Por exemplo
A && B && C && D || E
Use esses operadores lógicos com moderação. Eles podem rapidamente levar a um código ilegível, ou a uma lógica difícil de entender.
Por exemplo, você pode pensar que estes são iguais:
if A; then B; else C; fi
A && B || C
A diferença é: quando o C é executado?
Aqui estão alguns exemplos em que as listas de comandos podem simplificar o código.
Suponha que você tenha um arquivo de dados com informações sobre triângulos, e que cada triângulo seja representado por três linhas separadas que contêm os lados do triângulo.
$ cat triangle.dat
3
4
5
9
12
14
Você pode usar um laço while em que a condiçã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
Supondo que is_pythagorean seja 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 alguma forma de asserção em que uma exceção é lançada se alguma 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"
Listas de comandos longas ficam difíceis de ler muito rápido. Usar quebras de linha com generosidade pode ajudar bastante.
Considere este exemplo em que uma palavra é adicionada a um array se duas condições forem atendidas.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
O Bash permite adicionar 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 despercebido no fim da linha. Usar uma continuação de linha significa que você pode colocar o operador primeiro, o que deixa mais óbvio que a lista está continuando:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Uma continuação de linha é a sequência de dois caracteres "barra invertida" e "nova linha" (\ + \n).
Quando o Bash encontra essa sequência, ela é simplesmente removida do código, continuando assim a linha atual com a linha seguinte.
Tome cuidado para não deixar nenhum espaço 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