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.