Corso Linux / Standard input, output ed error: redirezioni e pipe in Bash

Un comando della shell usa normalmente tre canali: standard input (stdin, ingresso), standard output (stdout, uscita normale) e standard error (stderr, diagnostica). In Bash, < legge l’ingresso da un file, > e >> salvano l’uscita normale e 2> salva gli errori. La pipe | collega l’uscita di un comando all’ingresso del successivo: seguire questi percorsi spiega dove finiscono dati e messaggi.

Questa lezione segue i flussi tra comandi e file. Le condizioni di successo e insuccesso usate da && e || sono trattate nella lezione sull’esecuzione dei comandi nella shell (in inglese).

Distinguere i tre flussi standard

I tre canali sono identificati da numeri, chiamati descrittori di file: sono riferimenti che il processo usa per leggere o scrivere. In un terminale interattivo sono spesso collegati a tastiera e schermo; una redirezione o una pipe può cambiare tali collegamenti.

Numero Flusso Ruolo principale
0 Standard input (stdin) Dati letti dal comando
1 Standard output (stdout) Risultati normali
2 Standard error (stderr) Messaggi diagnostici e di errore

Le due uscite possono comparire insieme sullo schermo, ma rimangono canali distinti. Per questo > può salvare i risultati in un file lasciando i messaggi di errore visibili nel terminale. Il numero in 2> seleziona precisamente stderr; > senza numero seleziona stdout.

Creare un file per gli esercizi

Gli esempi presuppongono Bash. Esegui una riga alla volta: se ~/linux-course-14-practice esiste già, scegli un nome nuovo e sostituiscilo in entrambi i comandi. Se mkdir o cd fallisce, fermati. Una redirezione >, nelle normali impostazioni di Bash, svuota un file esistente prima di riscriverlo.

mkdir ~/linux-course-14-practice
cd ~/linux-course-14-practice
pwd

Verifica che pwd mostri la nuova directory dentro la tua home; il solo fatto che stampi un percorso non dimostra che il precedente cd sia riuscito. Usa esclusivamente file nuovi di esercitazione. Ora salva due righe e aggiungine una terza: >> scrive in fondo senza cancellare il contenuto precedente.

printf 'alpha\nbeta\n' > source.txt
printf 'gamma\n' >> source.txt
cat source.txt

cat deve mostrare, nell’ordine, alpha, beta e gamma. Le sequenze \n di printf creano i fine riga. Se ripeti soltanto l’aggiunta di gamma, otterrai una riga in più ogni volta: per ripristinare l’esercizio, riesegui entrambe le righe printf, iniziando dalla prima con >. Questa ricreazione deve riguardare solo source.txt della directory di prova.

Fornire un ingresso e salvare l’uscita normale

< collega un file a stdin. Nel comando seguente, wc -l legge quel flusso senza ricevere un nome di file come argomento e stampa il numero 3. GNU wc -l conta i caratteri di fine riga: qui ogni riga, inclusa l’ultima, ne possiede uno.

wc -l < source.txt

Con wc -l source.txt, invece, viene normalmente mostrato anche il nome del file dopo il conteggio. Il contenuto letto è lo stesso; cambia il modo in cui viene fornito al programma.

Per salvare stdout usa >. La prima riga seguente scrive la riga contenente beta in matches.txt, senza mostrarla nel terminale; la seconda legge il file e la mostra.

grep 'beta' source.txt > matches.txt
cat matches.txt

Devi vedere beta. Non è grep a interpretare >: è Bash ad aprire la destinazione prima di avviare il comando. Un nome sbagliato può quindi cancellare un file importante anche se il comando successivamente fallisce. Non usare mai lo stesso file come sorgente e destinazione, per esempio grep 'beta' source.txt > source.txt: il file verrebbe svuotato prima della lettura.

Se l’apertura della destinazione fallisce, per esempio perché la directory indicata non esiste, la redirezione fallisce e il comando non procede normalmente. Controlla il messaggio: un file vuoto non prova che l’elaborazione sia riuscita o che non esistano corrispondenze.

Gestire separatamente i messaggi di errore

Quando ls non trova un file, scrive un messaggio diagnostico su stderr. Nella directory nuova, assicurati che absent-14.txt non esista; il seguente esempio salva il messaggio in errors.txt e poi lo legge.

ls absent-14.txt 2> errors.txt
cat errors.txt

Il testo preciso dipende dalla lingua e dalla versione di ls, ma deve indicare che il file non esiste. La prima riga non mostra la diagnosi sullo schermo perché stderr è rediretto; è cat nella seconda riga a mostrarne il contenuto. Salvare un messaggio di errore non trasforma il fallimento di ls in un successo. Anche lo stato finale del blocco può essere quello di cat: per controllare lo stato di ls, leggi $? subito dopo la sua riga, prima di eseguire altro.

Per unire stdout e stderr in un file, Bash permette comando > all.txt 2>&1. 2>&1 duplica su stderr la destinazione attuale di stdout; il simbolo & fa parte di questa sintassi e qui non avvia un processo in background.

ls absent-14.txt > all.txt 2>&1
cat all.txt

Bash applica le redirezioni da sinistra a destra. Prima stdout viene collegato ad all.txt, poi stderr riceve la stessa destinazione. Invertendo l’ordine in ls absent-14.txt 2>&1 > all.txt, stderr conserva invece la precedente destinazione di stdout: in un terminale interattivo il messaggio rimane sullo schermo, mentre il file riceve solo stdout e in questa prova resta vuoto. Anche invertendo l’ordine, > può svuotare il file di destinazione.

Collegare due comandi con una pipe

La pipe | invia stdout del comando a sinistra a stdin del comando a destra. Questo esempio seleziona le righe che contengono beta e ne conta i fine riga:

grep 'beta' source.txt | wc -l

Con i tre record iniziali ottieni 1. I dati intermedi passano tra i processi senza richiedere un file temporaneo esplicito. Una pipe semplice non include automaticamente stderr: un errore del primo comando può ancora comparire nel terminale.

Per impostazione predefinita, lo stato di uscita della pipeline in Bash è quello dell’ultimo comando. Se sostituisci soltanto beta con delta, che non compare nel file, grep non trova corrispondenze e restituisce 1; wc -l stampa però 0 e può terminare con stato 0. La pipeline può quindi risultare riuscita pur senza corrispondenze. Ripristina beta per tornare al risultato 1.

L’opzione Bash pipefail, quando attiva, fa usare il più a destra tra gli stati non nulli della pipeline, oppure 0 se tutti i comandi riescono. Cambia quindi l’interpretazione dello stato, senza cambiare il conteggio stampato; va scelta consapevolmente negli script e non elimina la necessità di distinguere “nessuna corrispondenza” da un errore operativo.

Quando il risultato sorprende, prova separatamente ciascun comando e verifica quale flusso produce dati o diagnosi. La redirezione cambia i collegamenti di un comando con file o descrittori; la pipe collega due comandi. Nessuna delle due garantisce da sola che l’elaborazione sia corretta.

Documentazione ufficiale

Il manuale di Bash distribuito da Debian, nelle sezioni REDIRECTION e Pipelines, documenta l’ordine delle redirezioni, la duplicazione dei descrittori e pipefail. Il manuale di GNU wc precisa che -l conta i fine riga.