tee e xargs sono due piccoli strumenti che completano la pipe. tee divide un flusso: lo mostra a schermo e lo salva in un file nello stesso momento. xargs lo trasforma: prende l'elenco che gli arriva e lo usa per costruire ed eseguire altri comandi. Insieme permettono di fare in una riga cose che altrimenti richiederebbero uno script.
Prima di cominciare: input standard e argomenti
Un comando può ricevere dati in due modi diversi, ed è il punto da capire per usare bene questi due strumenti:
- come argomenti, scritti dopo il nome: in
rm file1 file2,file1efile2sono argomenti; - come input standard (stdin), il flusso di dati che arriva da una pipe o da un reindirizzamento.
Alcuni comandi, come sort, wc o grep, leggono volentieri lo stdin. Altri, come rm, cp, mkdir, kill, lo ignorano e vogliono solo argomenti. Per questo ls | rm non funziona: rm non guarda ciò che arriva dalla pipe. xargs serve a colmare questa distanza.
Cosa sono
tee prende il nome dal raccordo a T degli impianti idraulici: il flusso entra da una parte e si divide in due uscite. Fa parte del pacchetto GNU coreutils (/usr/bin/tee).
xargs è l'abbreviazione di extended arguments, "argomenti estesi". Fa parte del pacchetto GNU findutils, lo stesso di find (/usr/bin/xargs), ed è nato per lavorare con lui.
Cosa fanno
teelegge lo standard input e lo scrive sia sullo standard output sia in uno o più file. Il testo continua il suo cammino nella pipe, e intanto ne resta una copia.xargslegge dallo standard input un elenco di elementi (separati da spazi o a capo) e li passa come argomenti a un comando, lanciandolo una o più volte. Se il comando non è indicato, usaecho.
Come si usano
$ comando | tee [opzioni] file...
$ comando | xargs [opzioni] [comando [argomenti]]tee
Esempio 1: mostrare e salvare insieme
$ uname -srm | tee sistema.txt
Linux 6.8.0-45-generic x86_64
$ cat sistema.txt
Linux 6.8.0-45-generic x86_64Il risultato compare a schermo e viene scritto anche in sistema.txt. Con il solo reindirizzamento > sistema.txt non vedresti nulla.
Esempio 2: aggiungere in coda con -a
$ echo "controllo eseguito" | tee -a registro.txt
controllo eseguitoPer impostazione predefinita tee sovrascrive il file, come farebbe >. Con -a (append) aggiunge invece in fondo, come >>.
Esempio 3: scrivere in più file
$ date | tee data1.txt data2.txtTutti i nomi indicati dopo le opzioni ricevono una copia.
Esempio 4: tee nel mezzo di una pipeline
$ du -sh ~/* 2>/dev/null | sort -h | tee dimensioni.txt | tail -n 3
2,1G /home/enzo/Immagini
3,4G /home/enzo/Video
5,2G /home/enzo/ArchivioA dimensioni.txt arriva l'elenco completo e ordinato, mentre a schermo ne compaiono solo le ultime tre righe. È utile per salvare un risultato intermedio senza interrompere la catena.
Esempio 5: registrare l'output di un comando con tutti i messaggi
$ ./installa.sh 2>&1 | tee installazione.logCon 2>&1 anche i messaggi di errore (stderr) vengono inviati nella pipe insieme all'output normale, e tee li mostra e li salva. È il modo più semplice per tenere un registro di un'installazione o di una compilazione e continuare a seguirla a schermo. In Bash esiste la scorciatoia |&, equivalente a 2>&1 |.
Esempio 6: scrivere un file di sistema con sudo
$ echo "127.0.0.1 miosito.local" | sudo tee -a /etc/hosts
127.0.0.1 miosito.local
$ echo "127.0.0.1 miosito.local" | sudo tee -a /etc/hosts > /dev/nullÈ la soluzione al problema del reindirizzamento visto negli articoli su sudo ed echo: sudo echo ... >> file non funziona, perché il reindirizzamento lo fa la tua shell senza privilegi. Qui invece è tee a scrivere il file, e lo fa con sudo. Il secondo comando aggiunge > /dev/null per non vedere ripetuto il testo a schermo.
Esempio 7: creare un file di configurazione di più righe
$ sudo tee /etc/nginx/conf.d/prova.conf > /dev/null << 'EOF'
server {
listen 8080;
root /var/www/prova;
}
EOFCon il here document, visto nell'articolo su cat, si scrive un testo di più righe direttamente in un file protetto.
Esempio 8: dividere il flusso verso più comandi
$ cat frutta.txt | tee >(sort -u > uniche.txt) >(wc -l > righe.txt) > /dev/nullLa forma >(comando), detta process substitution, funziona in Bash e Zsh (non in sh) e permette a tee di inviare il flusso a più comandi, e non solo a file. Qui lo stesso elenco viene ordinato senza doppioni in un file e contato in un altro. I comandi girano in parallelo, e subito dopo il ritorno del prompt i file possono non essere ancora completi.
Esempio 9: uso negli script
#!/bin/bash
set -o pipefail
LOG="$HOME/backup.log"
registra() {
echo "$(date '+%F %T') $*" | tee -a "$LOG"
}
registra "Avvio backup"
tar -czf "$HOME/backup.tar.gz" -C "$HOME" Documenti 2>&1 | tee -a "$LOG"
registra "Backup terminato"Ogni messaggio viene mostrato e anche aggiunto al registro. L'istruzione set -o pipefail serve perché, in una pipeline, per impostazione predefinita conta solo l'esito dell'ultimo comando (tee, che riesce sempre): con pipefail un errore di tar si riflette nel codice di uscita della pipeline ($?), che puoi controllare subito dopo.
xargs
Esempio 10: il comportamento di base
$ echo "uno due tre" | xargs
uno due tre
$ echo "uno due tre" | xargs -n 1
uno
due
treSenza un comando, xargs usa echo. Con -n 1 passa un solo elemento per volta, e quindi echo viene lanciato tre volte. È un buon modo per capire come xargs spezza l'input.
Esempio 11: eliminare i file trovati da find
$ find . -name "*.tmp" -print0 | xargs -0 rmIl comando find produce l'elenco dei file e xargs lo passa a rm come argomenti. L'accoppiata -print0 e -0 è fondamentale: separa i nomi con un carattere nullo invece che con lo spazio. Senza di essa, un file chiamato mio file.tmp verrebbe spezzato in due elementi, mio e file.tmp, e rm proverebbe a eliminare due file che forse non esistono, o peggio, che esistono ma non volevi toccare.
Esempio 12: guardare prima di eseguire
$ find . -name "*.bak" -print0 | xargs -0 echo rm
rm ./vecchio.bak ./nota.bak
$ find . -name "*.bak" -print0 | xargs -0 -t rm
rm ./vecchio.bak ./nota.bak
$ find . -name "*.bak" -print0 | xargs -0 -p rm
rm ./vecchio.bak ./nota.bak ?...yTre modi per evitare brutte sorprese: anteporre echo al comando (si vede cosa verrebbe eseguito, come per rm *.tmp), usare -t (trace), che stampa ogni comando prima di lanciarlo, oppure -p (prompt), che chiede conferma.
Esempio 13: quanti elementi per comando con -n
$ printf '%s\n' a b c d e | xargs -n 2 echo
a b
c d
eCon -n 2 ogni esecuzione riceve al massimo due argomenti.
Esempio 14: scegliere dove va l'elemento con -I
$ cat nomi.txt
Rossi
Bianchi
Verdi
$ cat nomi.txt | xargs -I{} mkdir -p "clienti/{}"
$ ls clienti
Bianchi Rossi VerdiDi default xargs mette gli elementi in fondo al comando. Con -I{} (il segno {} è un segnaposto a scelta) decidi tu dove inserirli, e il comando viene eseguito una volta per ogni riga. Con -I l'input viene separato solo dagli a capo, quindi i nomi con spazi restano interi.
Esempio 15: più processi in parallelo con -P
$ find /var/log/miaapp -name "*.log" -print0 | xargs -0 -n 1 -P 4 gzip
$ cat indirizzi.txt | xargs -n 1 -P 4 curl -sOCon -P 4 xargs esegue fino a quattro comandi in contemporanea. Il primo comprime quattro log alla volta, il secondo scarica quattro file alla volta. Per usare tutti i processori del computer: -P "$(nproc)". Con l'esecuzione parallela l'ordine di completamento non è prevedibile, e l'output di processi diversi può mescolarsi.
Esempio 16: cercare un testo nei file trovati
$ find . -name "*.php" -print0 | xargs -0 grep -l "mysql_connect"
./vecchio/db.php
./vecchio/utenti.phpEquivale a find ... -exec grep -l "mysql_connect" {} +, visto nell'articolo su find. Con xargs si possono però aggiungere -P per il parallelismo e usare qualsiasi elenco, e non solo quello di find.
Esempio 17: separatore personalizzato con -d
$ echo -n "a,b,c" | xargs -d ',' -n 1
a
b
cCon -d si sceglie il carattere che separa gli elementi. Il -n di echo evita l'a capo finale, che altrimenti verrebbe considerato parte dell'ultimo elemento. Una forma molto utile è -d '\n': separa solo agli a capo, e i nomi con spazi restano interi, quando l'elenco non proviene da find -print0.
Esempio 18: tee e xargs insieme
$ grep -rl "ERROR" /var/log/miaapp | tee file_con_errori.txt | xargs -d '\n' ls -lhLa ricerca produce l'elenco dei file con errori, tee lo salva in file_con_errori.txt e xargs lo passa a ls -lh per vederne le dimensioni.
Esempio 19: fermare i processi trovati
$ pgrep -f "miascript.py" | xargs -r killIl comando pgrep stampa i PID e xargs li passa a kill come argomenti. Con -r (no-run-if-empty) il comando non viene eseguito se l'elenco è vuoto.
Esempio 20: uso negli script
#!/bin/bash
LOG_DIR="/var/log/miaapp"
[ -d "$LOG_DIR" ] || { echo "Cartella non trovata" >&2; exit 1; }
# comprime in parallelo i log più vecchi di 7 giorni
find "$LOG_DIR" -type f -name "*.log" -mtime +7 -print0 \
| xargs -0 -r -n 1 -P "$(nproc)" gzip
echo "Compressione completata"Opzioni utili
Per tee:
-a: aggiunge in coda ai file invece di sovrascriverli.-i: ignora il segnale di interruzione (Ctrl+C).-p: continua a scrivere sui file anche se l'uscita verso la pipe si chiude (vedi le avvertenze).
Per xargs:
-0: separa gli elementi con il carattere nullo (si abbina afind -print0).-d carattere: sceglie il separatore (per esempio-d '\n').-n N: al massimo N argomenti per esecuzione.-I{}: segnaposto per il punto in cui inserire l'elemento, un'esecuzione per elemento.-P N: fino a N esecuzioni in parallelo.-r: non esegue il comando se l'input è vuoto.-t: stampa ogni comando prima di eseguirlo;-p: chiede conferma.-L N: usa N righe di input per ogni esecuzione.
Attenzione a
teesovrascrive: senza-a, il file indicato viene svuotato. E non usare lo stesso file come ingresso e come uscita (cat file | tee file): il file viene svuotato prima di essere letto, come percat file > file.- L'esito di una pipeline è quello dell'ultimo comando:
comando | tee logriesce sempre, anche secomandofallisce. In Bash si usaset -o pipefailoppure si legge${PIPESTATUS[0]}subito dopo. - L'errore va in un altro canale:
teeregistra solo lo standard output. Per includere gli errori scrivi2>&1prima della pipe. - Con
sudodavanti al comando sbagliato:sudo comando | tee filedà i privilegi acomandoma non atee, e la scrittura può fallire. Il privilegio serve atee:comando | sudo tee file. - Quando si chiude il lato destro: in
comando | tee file | head -n 3, quandoheadha letto le sue righe e si chiude,teericeve un errore di scrittura e termina, e il file può restare incompleto. Contee -pcontinua a scrivere sul file. - Colori e programmi interattivi: molti programmi, quando l'output passa da una pipe, smettono di usare i colori e di visualizzare le barre di avanzamento, perché non sono più collegati a un terminale. Con alcuni programmi si può forzare (
ls --color=always), ma nei file di registro i codici di colore diventano caratteri indesiderati. Inoltre, un programma che mette l'output in un buffer può mostrarlo a blocchi:stdbuf -oL comandolo forza riga per riga. - I nomi con spazi in
xargs: per impostazione predefinitaxargssepara su spazi e a capo, e interpreta anche apici e barre rovesciate. Un nome con spazio viene spezzato e uno con un apostrofo può dare l'erroreunmatched single quote. Usa semprefind -print0 | xargs -0oppure-d '\n'. - Mai
ls | xargs: l'output dilsnon è fatto per essere elaborato da altri programmi. Usafindcon-print0o i caratteri jolly. - Input vuoto: con GNU
xargs, se non arriva nulla, il comando viene eseguito lo stesso, una volta, senza argomenti. Conrmotterresti un errore di operando mancante, ma con altri comandi l'effetto può essere ben diverso (lsmostrerebbe la cartella corrente). Aggiungi-r. Su macOS e sui sistemi BSD il comportamento predefinito è il contrario e l'opzione-rnon serve. - Più esecuzioni, non una: se i parametri sono molti,
xargslancia il comando più volte, a gruppi. I comandi che si aspettano di vedere tutti i file insieme possono dare risultati sbagliati:find ... | xargs tar -cf archivio.tarpuò creare un archivio con solo l'ultimo gruppo, perché ogni esecuzione ricrea il file. In quel casotarha un'opzione dedicata:find ... -print0 | tar --null -czf archivio.tar.gz -T -. Anche i totali diwccompaiono più volte. - Un solo carattere in più:
xargs rmcon un elenco sbagliato elimina davvero. Prima di una cancellazione di massa, anteponiechoo usa-p. - L'esito di
xargs: restituisce 0 se tutte le esecuzioni riescono, 123 se almeno una è terminata con un errore, e altri codici in casi particolari (per esempio 127 se il comando non esiste). - I comandi interattivi (come
vimoless) conxargsnon ricevono la tastiera, perché il loro input è occupato dall'elenco. Con GNUxargsl'opzione-oli riapre sul terminale.
Riepilogo
| Comando | Cosa fa |
|---|---|
comando | tee file | Mostra l'output e lo salva in un file |
comando | tee -a file | Come sopra, aggiungendo in coda |
comando 2>&1 | tee log | Registra anche i messaggi di errore |
echo "x" | sudo tee -a /etc/file | Scrive in un file protetto |
… | tee file | altro | Salva un risultato intermedio e continua la catena |
… | xargs comando | Passa l'elenco come argomenti al comando |
find … -print0 | xargs -0 comando | Come sopra, sicuro con spazi e caratteri strani |
… | xargs -n 1 comando | Un elemento per esecuzione |
… | xargs -I{} comando {} altro | Inserisce l'elemento dove vuoi |
… | xargs -P 4 comando | Esegue fino a 4 comandi in parallelo |
… | xargs -r comando | Non esegue nulla se l'elenco è vuoto |
… | xargs echo comando / -t / -p | Anteprima, traccia o conferma prima di eseguire |