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.txtscrive l'output nel file, sostituendone il contenuto se esiste già.comando >> file.txtaggiunge l'output in fondo al file.comando 2> errori.txtsalva solo i messaggi di errore.comando > tutto.txt 2>&1manda 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:
cut -d' ' -f1estrae il primo campo di ogni riga, cioè l'indirizzo IP;sortordina gli indirizzi, così quelli uguali diventano adiacenti;uniq -cunisce le righe uguali consecutive e ne conta le occorrenze;sort -rnordina numericamente in modo decrescente;head -10mostra 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 ; beseguebdopoa, qualunque sia l'esito;a && beseguebsolo seaè riuscito;a || beseguebsolo seaè 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>&1li unisce all'output.- La pipe
|passa l'output di un comando al successivo e permette analisi rapide di log e testi. teesalva e mostra insieme; consudo teesi scrive in file di sistema.- Preferisci
&&a;quando un comando dipende dal successo del precedente.