
Devo confrontare due file CSV che forse contengono gli stessi dati: nei due file le righe sono ordinate diversamente. Voglio farlo son un solo comando da bash.
Bash è un interprete di comandi (shell) che normalmente viene usato attraverso un emulatore di terminale come GNOME Terminal. Abbiamo parlato di emulazione di terminale qui a proposito di applicazioni che scambiano messaggi.
I due file sono stati prodotti da una stessa sorgente in due momenti diversi e voglio capire se l’esportazione produce gli stessi record oppure no.
Con diff il confronto diretto non serve a molto:
diff file1.csv file2.csv
perché righe uguali in posizioni diverse vengono naturalmente segnalate come differenze.
Come prima strategia potrei allora ordinare prima i due file e poi confrontarli
sort file1.csv > /tmp/file1.sorted sort file2.csv > /tmp/file2.sorted diff /tmp/file1.sorted /tmp/file2.sorted
Funziona, ma sto creando due file temporanei che in realtà non mi interessano. Vorrei fare tutto con un solo comando, se possibile.
Bash offre una soluzione molto più elegante:
diff <(sort file1.csv) <(sort file2.csv)
Sommario
Ma che cos’è <(...)?
A prima vista viene spontaneo interpretare < come la normale redirezione dello standard input. Ma qui è un’altra cosa.
Il costrutto
<(comando)
si chiama process substitution.
Bash esegue comando e rende il suo output disponibile attraverso qualcosa che, dal punto di vista del programma chiamante, si comporta come un file (anche se non lo è propriamente).
Possiamo vederlo direttamente:
echo <(sort file1.csv)
Su un tipico sistema Linux otterremo qualcosa del genere:
/dev/fd/63
Quindi il nostro comando
diff <(sort file1.csv) <(sort file2.csv)
diventa concettualmente qualcosa come:
diff /dev/fd/63 /dev/fd/62
diff crede di ricevere due file, mentre Bash gli sta fornendo direttamente l’output dei due processi sort.
Perché non basta una pipe?
Una pipe:
comando1 | comando2
collega lo standard output di comando1 allo standard input di comando2.
Ma diff ha bisogno di confrontare due sorgenti distinte:
diff sorgente1 sorgente2
La process substitution permette proprio di trasformare l’output di ciascun comando in una sorgente utilizzabile come argomento.
È quindi un piccolo ma potente cambio di prospettiva:
comando | altro_comando
significa:
usa questo output come standard input
mentre
<(comando)
significa:
usa questo output come se fosse un file
Ed ecco che un confronto che sembrava richiedere tre comandi e due file temporanei diventa una sola riga:
diff <(sort file1.csv) <(sort file2.csv)
Una di quelle piccole comodità di Bash che, una volta scoperte, si finisce per riutilizzare spesso.
Una nota storica
Il nome bash è un acronimo di “Bourne-Again SHell” (come riporta l’immagine introduttiva del post), un gioco di parole su Stephen Bourne, l’autore del predecessore diretto dell’attuale shell Unix sh, che apparve nella settima edizione della versione di Unix sviluppata all’unità Research dei Bell Labs.
Bourne-again shell è assonante con born again shell, ossia shell rinata.
Riferimenti
- With a little help from my friend ChatGPT
- Bash (Bourne Again SHell)
- GNU Linux man (Process substitution)
- bash su emulatore di terminale Terminal di GNOME
Commenti recenti