La filosofia Unix dice che ogni programma dovrebbe fare una cosa sola e farla bene. La potenza non sta quindi nel singolo comando, ma nel modo in cui li metti in fila: la redirezione porta i dati verso un file o un descrittore, la pipe li porta verso un altro processo. Quasi tutto il lavoro quotidiano su una shell è comporre catene di questo tipo.

La pipe collega stdout a stdin

La barra verticale prende lo standard output del comando a sinistra e lo attacca allo standard input di quello a destra. La shell non li esegue in sequenza: li avvia tutti insieme e i dati scorrono man mano che vengono prodotti.

$ grep 'model name' /proc/cpuinfo | uniq
model name      : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
$ ps -ef | grep sshd | wc -l
3

Le pipe si concatenano quanto serve e si mescolano con le redirezioni, perché fanno cose diverse: comando | altro > risultato.txt filtra e poi salva. Vale però una regola: ogni canale ha una sola destinazione, quindi con le sole redirezioni non puoi mandare lo stesso output a un file e insieme a un altro comando. Per quello serve tee.

Nella pipe passa solo stdout, non stderr

È la trappola più frequente. Un messaggio di errore compare sullo schermo ma non entra nella catena, perché viaggia sul descrittore 2:

$ make | tee log.txt
make: *** No targets specified and no makefile found.  Stop.
$ cat log.txt
$

Il file resta vuoto. Per catturare anche gli errori devi prima dirottare stderr su stdout, e l’ordine non è negoziabile: 2>&1 va scritto prima della barra verticale, non dopo.

$ make 2>&1 | tee log.txt
$ ./installa.sh 2>&1 | grep -i error

Bash offre anche la forma abbreviata |&, ma la sequenza da avere in punta di dita, e da saper digitare senza opzioni davanti, è comando 2>&1 | comando.

tee: vedere e salvare nello stesso passaggio

tee legge lo standard input, lo scrive nei file che gli indichi e lo ripete sul proprio standard output, così la catena può proseguire.

$ dmesg | tee diario.txt | grep -i error
$ ./backup.sh 2>&1 | tee -a /var/log/backup.log

Senza opzioni tee sovrascrive il file; -a accoda. C’è poi un uso che risolve un fastidio quotidiano: in sudo comando > /etc/file la redirezione la esegue la shell, che è ancora il tuo utente, e ottieni “Permission denied”. La forma che funziona è comando | sudo tee /etc/file.

xargs: dall’input agli argomenti

Molti comandi (mv, rm, cp, ls, tar) non leggono lo standard input: vogliono argomenti sulla riga. xargs fa da traduttore, prende quello che riceve e lo aggiunge come argomenti al comando che gli passi.

$ find /var/log -name '*.gz' | xargs ls -lh
$ find . -name '*.mp4' -print0 | xargs -0 du -h | sort -h
$ find . -name '*.mp4' -print0 | xargs -0 -I FILE mv FILE ./video/

La coppia find -print0 con xargs -0 è una di quelle da sapere a memoria, non da riconoscere: separa i percorsi con un carattere nullo e sopravvive ai nomi con spazi. Le altre opzioni che l’esame chiede: -n 1 esegue il comando con un argomento alla volta, -L 1 con una riga alla volta, -I definisce un segnaposto (qui FILE, molti usano una coppia di parentesi graffe vuote) da piazzare dove vuoi, visto che di default gli argomenti finiscono in coda. Aggiungi -t per vedere il comando prima che parta e -p per farti chiedere conferma. Quando serve un’esecuzione per ogni file, l’opzione -exec di find è spesso più diretta.

Sostituzione di comando: l’output diventa una parola

Il terzo modo di riusare un output è farlo diventare testo nella riga di comando. Si scrive $(comando) oppure fra apici inversi.

$ mkdir $(date +%Y-%m-%d)
$ OGGI=`date +%F`
$ kill -TERM $(pgrep -f backup.sh)

Le due forme sono equivalenti; conviene $(), che si annida e si legge meglio. La differenza rispetto a xargs è sostanziale: la sostituzione costruisce una riga sola, quindi con migliaia di risultati rischi il limite di lunghezza della riga di comando e perdi gli spazi nei nomi, mentre xargs spezza il lavoro in più esecuzioni e con -0 non si fa ingannare.