Percursos
/
Bash
Bash
/
Programa
/
Pipelines e listas de comandos
Pi

Pipelines e listas de comandos em Bash

{one: "1 exercício", many: "%{count} exercícios", other: "%{count} exercícios"}

Sobre Pipelines e listas de comandos

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.

Entrada e saída

Antes de começarmos, uma breve introdução à entrada/saída.

Os processos têm "canais de E/S padrão".

  • Um processo pode consumir entrada no "stdin".
  • Um processo pode emitir saída no "stdout".
  • Um processo pode emitir saída de erro no "stderr".

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.

Pipelines

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.

Advanced
  • 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á:

    • zero se todos os comandos tiverem sucesso, caso contrário
    • o último estado de saída diferente de zero dos comandos do pipeline.

Listas de comandos

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
  • B é executado se A tiver sucesso,
  • C é executado se A e B tiverem sucesso,
  • D é executado se A, B e C tiverem sucesso,
  • E é executado se qualquer de A, B, C ou D falhar.
Caution

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?

  • No primeiro excerto (a condicional), o C só é executado se o A falhar.
  • No segundo excerto, o C é executado se o A falhar ou se o A tiver sucesso mas o B falhar!

Utilizações das listas de comandos

Aqui ficam alguns exemplos em que as listas de comandos podem simplificar o código.

Ler blocos de linhas de um ficheiro

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
Asserções

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"

Considerações de estilo

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")
Note

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
Editar via GitHub A ligação abre numa nova janela ou separador