>_ Sistemi operativi e Linux

Pipe e redirezione nella shell: stdin, stdout e stderr

Come combinare i comandi Linux con pipe e redirezione: standard input, output ed error, operatori > >> 2> e |, tee, /dev/null ed esempi pratici sui log.

La forza della riga di comando non sta nei singoli programmi, ma nella possibilità di collegarli. Ogni comando fa una cosa sola, e la shell permette di passare il risultato dell'uno all'altro come in una catena di montaggio. Il meccanismo che lo rende possibile è semplice e vale la pena capirlo a fondo: sono tre canali di comunicazione e una manciata di operatori.

I tre flussi standard

Ogni processo nasce con tre canali aperti, identificati da un numero:

Numero Nome Uso normale
0 stdin (standard input) ciò che il programma legge, di solito la tastiera
1 stdout (standard output) il risultato, di solito lo schermo
2 stderr (standard error) messaggi di errore e diagnostica, anch'essi sullo schermo

Il fatto che risultati ed errori viaggino su canali separati è la chiave di tutto: si possono salvare i risultati in un file e continuare a vedere gli errori, o viceversa.

Redirigere l'output su un file

  • comando > file.txt scrive l'output nel file, sostituendone il contenuto se esiste già.
  • comando >> file.txt aggiunge l'output in fondo al file.
  • comando 2> errori.txt salva solo i messaggi di errore.
  • comando > tutto.txt 2>&1 manda sia output sia errori nello stesso file.

L'ultima forma confonde molti: 2>&1 significa «manda il canale 2 dove sta andando il canale 1». L'ordine conta: va scritta dopo la redirezione dell'output, altrimenti gli errori finiscono ancora sullo schermo. Bash offre anche la scorciatoia &> tutto.txt.

Un errore classico è sort dati.txt > dati.txt: la shell svuota il file di destinazione prima che sort cominci a leggerlo, e il risultato è un file vuoto. Si scrive su un file diverso e poi lo si rinomina.

Leggere l'input da un file

L'operatore < fa leggere al comando il contenuto di un file come se fosse digitato: wc -l < elenco.txt conta le righe. Esiste anche lo here document, utile negli script per passare più righe di testo:

cat <<FINE > saluto.txt
Prima riga
Seconda riga
FINE

/dev/null: il cestino dell'output

/dev/null accetta qualsiasi dato e lo scarta. Serve quando un comando produce messaggi che non interessano:

find / -name "*.conf" 2>/dev/null

Qui la ricerca su tutto il disco produrrebbe decine di righe «Permesso negato»; mandandole in /dev/null restano solo i risultati utili. Va usato con giudizio: nascondere tutti gli errori di uno script significa non accorgersi quando qualcosa va storto.

La pipe: collegare due comandi

L'operatore | collega lo stdout di un comando allo stdin del successivo. I dati scorrono direttamente dall'uno all'altro, senza file intermedi.

ps aux | grep nginx

ps elenca i processi, grep tiene solo le righe che contengono «nginx». Le pipe si possono concatenare quanto si vuole, e qui la riga di comando mostra la sua potenza.

Un esempio concreto: chi bussa al server

Supponiamo di voler sapere quali indirizzi IP hanno fatto più richieste a un server web, leggendo il log di accesso:

cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head -10

Passo per passo:

  1. cut -d' ' -f1 estrae il primo campo di ogni riga, cioè l'indirizzo IP;
  2. sort ordina gli indirizzi, così quelli uguali diventano adiacenti;
  3. uniq -c unisce le righe uguali consecutive e ne conta le occorrenze;
  4. sort -rn ordina numericamente in modo decrescente;
  5. head -10 mostra i primi dieci.

Cinque programmi semplici, ciascuno noto da decenni, producono in un secondo una statistica che altrimenti richiederebbe uno script. Nel mio lavoro questo tipo di catena è spesso il primo strumento per farsi un'idea di cosa sta succedendo su un sistema.

Pipe e stderr

La pipe trasporta solo lo stdout. Gli errori del primo comando continuano ad apparire sullo schermo e non vengono filtrati dal secondo. Se servono anche loro nella catena, si usa 2>&1 | oppure, in Bash, |&.

tee: salvare e vedere allo stesso tempo

tee legge lo stdin, lo scrive in un file e lo passa avanti. È utile quando vuoi conservare l'output di un'operazione lunga senza rinunciare a seguirla:

sudo apt upgrade | tee aggiornamento.log

Con tee -a il file viene esteso invece che sovrascritto. Un uso meno ovvio riguarda i permessi: echo testo > /etc/file con sudo davanti fallisce, perché la redirezione è eseguita dalla shell dell'utente normale. La forma corretta è echo testo | sudo tee /etc/file.

Comandi in sequenza

Oltre alla pipe esistono operatori che regolano l'ordine di esecuzione:

  • a ; b esegue b dopo a, qualunque sia l'esito;
  • a && b esegue b solo se a è riuscito;
  • a || b esegue b solo se a è fallito.

mkdir backup && cp dati.db backup/ copia il file solo se la cartella è stata creata davvero. Abituarsi a && invece di ; evita che un errore iniziale si propaghi in operazioni successive.

In sintesi

  • Ogni processo ha stdin, stdout e stderr: risultati ed errori viaggiano separati.
  • > sovrascrive, >> aggiunge, 2> redirige gli errori, 2>&1 li unisce all'output.
  • La pipe | passa l'output di un comando al successivo e permette analisi rapide di log e testi.
  • tee salva e mostra insieme; con sudo tee si scrive in file di sistema.
  • Preferisci && a ; quando un comando dipende dal successo del precedente.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica e in psicologia, unisce l'analisi tecnica alla comprensione del fattore umano.

Leggi anche