Tracce
/
Bash
Bash
/
Programma
/
Pipeline e liste di comandi
Pi

Pipeline e liste di comandi in Bash

{one: "1 esercizio", many: "%{count} esercizi", other: "%{count} esercizi"}

Informazioni su Pipeline e liste di comandi

Abbiamo visto come scrivere comandi semplici, in cui a un comando seguono i suoi argomenti. Ora vedremo come creare comandi più complessi componendo comandi semplici.

Input e output

Prima di iniziare, una breve introduzione all'input/output.

I processi hanno dei «canali di I/O standard».

  • Un processo può consumare input su «stdin».
  • Un processo può produrre output su «stdout».
  • Un processo può produrre output di errore su «stderr».

Il comando tr è un esempio molto puro di tutto questo. Tutto quello che fa è leggere il testo dal suo input, eseguire traslitterazioni di caratteri e stampare il testo risultante sul suo output.

Vedremo più avanti nel percorso come manipolare i canali «stdio».

Pipeline

Questa è una delle «killer feature» della programmazione in shell. Le pipeline permettono di creare trasformazioni sofisticate su un flusso di testo.

Per produrre un elenco ordinato di utenti:

cat /etc/passwd | cut -d : -f 1 | sort

Il simbolo di pipe (|) collega l'output di un comando all'input di un altro. cut legge l'output di cat e sort legge l'output di cut.

Advanced
  • Per impostazione predefinita, ogni comando di una pipeline viene eseguito in una subshell separata. (Una subshell è un processo figlio che è una copia della shell in esecuzione.)

  • Eseguire delle pipeline ha un costo in termini di prestazioni. Se ti ritrovi con pipeline lunghe fatte di comandi simili, valuta di combinarli in un unico comando. Per esempio, le pipeline che usano più istanze di grep, cut, sed, awk e tr in genere si possono combinare in un unico comando awk per maggiore efficienza.

  • Lo stato di uscita di una pipeline è lo stato di uscita dell'ultimo comando della pipeline. Questo significa che se un comando precedente è fallito ma l'ultimo comando è riuscito, lo stato di uscita dell'intera pipeline è zero.

    Esiste un'impostazione della shell che può cambiare questo comportamento; se l'impostazione «pipefail» è attivata (con set -o pipefail), allora lo stato di uscita della pipeline sarà:

    • zero se tutti i comandi riescono, altrimenti
    • lo ultimo stato di uscita non-zero dei comandi nella pipeline.

Liste di comandi

Una lista di comandi è una sequenza di pipeline separate da ; (o da una nuova riga), && o ||.

  • A; B è una lista di comandi in cui B viene eseguito dopo che A è terminato.
  • A && B, dove B viene eseguito solo se A riesce (esce con stato zero).
  • A || B, dove B viene eseguito solo se A fallisce (esce con stato non-zero).

Lo stato di uscita di una lista di comandi è lo stato di uscita dell'ultimo comando eseguito.

Gli operatori && e || si possono concatenare, così che il comando successivo venga eseguito in modo condizionale in base allo stato dei comandi precedenti. Per esempio

A && B && C && D || E
  • B viene eseguito se A riesce,
  • C viene eseguito se A e B riescono,
  • D viene eseguito se A, B e C riescono,
  • E viene eseguito se uno qualsiasi tra A, B, C e D fallisce.
Caution

Usa questi operatori logici con moderazione. Possono portare rapidamente a codice illeggibile o a una logica difficile da comprendere.

Per esempio, potresti pensare che questi due frammenti siano equivalenti:

if A; then B; else C; fi
A && B || C

La differenza è: quando viene eseguito C?

  • Nel primo frammento (l'istruzione if), C verrà eseguito solo se A fallisce.
  • Nel secondo frammento, C verrà eseguito se A fallisce o se A riesce ma B fallisce!

Usi delle liste di comandi

Ecco un paio di esempi in cui le liste di comandi possono semplificare il codice.

Leggere blocchi di righe da un file

Supponi di avere un file di dati che contiene informazioni su dei triangoli, e che ogni triangolo sia rappresentato da tre righe separate che contengono i lati del triangolo.

$ cat triangle.dat
3
4
5
9
12
14

Puoi usare un ciclo while in cui la condizione è composta da tre comandi read separati:

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

Supponendo che is_pythagorean sia un comando che determina se i tre lati soddisfano l'equazione di Pitagora, l'output sarebbe:

3:4:5 is pythagorean
9:12:14 is not pythagorean
Asserzioni

Molti linguaggi di programmazione hanno una forma di asserzione in cui viene lanciata un'eccezione se una condizione fallisce

assert(user == "Administrator", "wrong user");

Possiamo usare un operatore OR in Bash per simulare quella funzione:

die () { # a little convenience function
    echo "$*" >&2
    exit 1
}

[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]]   || die "incorrect password"

Considerazioni di stile

Le liste di comandi lunghe diventano difficili da leggere piuttosto in fretta. Un uso abbondante delle nuove righe può aiutare parecchio.

Considera questo esempio, in cui una parola viene aggiunta a un array se sono soddisfatte due condizioni.

[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")

Bash permette di aggiungere una nuova riga dopo una pipe o un operatore logico.

[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")

Tuttavia, l'operatore può sfuggire facilmente alla fine della riga. Usare una continuazione di riga permette di mettere l'operatore all'inizio, il che rende più evidente che la lista continua:

[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Note

Una continuazione di riga è la sequenza di due caratteri «backslash» e «newline» (\ + \n). Quando Bash vede quella sequenza, la rimuove semplicemente dal codice, continuando così la riga corrente con quella successiva. Fai attenzione a non lasciare spazi tra il backslash e il newline.

Ecco un altro esempio

printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort

oppure

printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort
Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda