Salta al contenuto principale

diff: confrontare file e cartelle

Inviato da tuxsa il

Hai modificato un file di configurazione e il sito non va più: cosa è cambiato rispetto a ieri? Due cartelle dovrebbero essere identiche dopo una copia con rsync: lo sono davvero? Con diff si confrontano due file, riga per riga, e si ottiene l'elenco preciso delle differenze.

Cos'è

diff (difference) fa parte del pacchetto diffutils, presente su ogni distribuzione. Confronta due file di testo e descrive quali righe vanno aggiunte, tolte o modificate per trasformare il primo nel secondo. È anche la base del lavoro con git e con le patch, i piccoli file che descrivono una modifica al codice e che si possono applicare con il comando patch.

Cosa fa

Legge i due file, trova la serie minima di differenze e le stampa. Se i file sono uguali non scrive nulla. Cosa importante per gli script, il codice di uscita dice il risultato: 0 se i file sono uguali, 1 se sono diversi, 2 se c'è stato un errore (per esempio un file inesistente). Per default non confronta permessi, proprietari o date: solo il contenuto.

Come si usa

$ diff [opzioni] file1 file2
$ diff [opzioni] cartella1 cartella2

Il primo file è il "vecchio" e il secondo il "nuovo": l'ordine conta, perché le differenze descrivono come passare dal primo al secondo. I messaggi di diff dipendono dalla lingua del sistema; con LC_ALL=C sono sempre in inglese. Per gli esempi usiamo due piccoli file:

$ cat frutta1.txt
mela
pera
arancia
limone
$ cat frutta2.txt
mela
pesca
arancia
kiwi
limone

Esempio 1: il formato normale

$ diff frutta1.txt frutta2.txt
2c2
< pera
---
> pesca
3a4
> kiwi

Senza opzioni diff usa il formato "normale". Ogni gruppo di differenze comincia con una sigla: i numeri a sinistra si riferiscono al primo file, quelli a destra al secondo, e la lettera in mezzo dice cosa è successo: c (change, riga modificata), a (add, righe aggiunte) e d (delete, righe eliminate). Quindi 2c2 vuol dire "la riga 2 del primo file è cambiata nella riga 2 del secondo", e 3a4 "dopo la riga 3 del primo file è stata aggiunta la riga 4 del secondo". Le righe con < sono quelle del primo file, le righe con > quelle del secondo.

Esempio 2: il formato unificato, quello da usare

$ diff -u frutta1.txt frutta2.txt
--- frutta1.txt   2026-10-27 07:42:51.846989027 +0100
+++ frutta2.txt   2026-10-27 07:42:51.846989027 +0100
@@ -1,4 +1,5 @@
 mela
-pera
+pesca
 arancia
+kiwi
 limone

Con -u (unified) l'output è più leggibile e mostra le righe vicine, per capire dove ci si trova. È anche il formato usato da git diff e dalle patch. Come leggerlo:

  • le due righe in alto sono i nomi dei file (--- il vecchio, +++ il nuovo) con la data di modifica;
  • @@ -1,4 +1,5 @@ dice che il blocco copre dalla riga 1 per 4 righe nel vecchio file, e dalla riga 1 per 5 righe nel nuovo;
  • le righe che cominciano con uno spazio sono uguali nei due file (il contesto);
  • le righe con - sono presenti solo nel vecchio file; quelle con + solo nel nuovo.

Per mostrare più o meno righe di contesto si usa -U1, -U5…: il numero dopo -U è quante righe di contesto vedere attorno a ogni modifica (il valore predefinito di -u è 3).

Esempio 3: affiancare i due file con -y

$ diff -y -W 50 frutta1.txt frutta2.txt
mela                    mela
pera                  | pesca
arancia                 arancia
                      > kiwi
limone                  limone
$ diff -y -W 50 --suppress-common-lines frutta1.txt frutta2.txt
pera                  | pesca
                      > kiwi

Con -y (side by side) i due file sono mostrati affiancati: a sinistra il primo, a destra il secondo, con un simbolo al centro: | riga diversa, > riga presente solo a destra, < riga presente solo a sinistra. -W imposta la larghezza totale in caratteri (per lo schermo, ad esempio, -W 160), e --suppress-common-lines nasconde le righe identiche, lasciando solo le differenze. Utile con file corti; con righe lunghe diventa illeggibile.

Esempio 4: un caso reale, la configurazione di nginx

$ cp -a nginx-vecchio.conf nginx-nuovo.conf
$ nano nginx-nuovo.conf
$ diff -u nginx-vecchio.conf nginx-nuovo.conf
--- nginx-vecchio.conf   2026-10-27 07:42:42.909557865 +0100
+++ nginx-nuovo.conf     2026-10-27 07:42:42.909557865 +0100
@@ -1,6 +1,6 @@
 server {
     listen 80;
-    server_name esempio.org;
+    listen 443 ssl;
+    server_name esempio.org www.esempio.org;
     root /var/www/sito;
-    index index.html;
 }

Il flusso consigliato prima di toccare un file importante: copiare l'originale (con cp -a), modificare la copia o l'originale con nano, e poi guardare diff -u per verificare di aver cambiato solo ciò che volevi. Qui si aggiunge l'ascolto sulla porta 443 con SSL, si aggiunge www al nome del sito e si toglie la riga index. Prima di ricaricare il servizio con systemctl reload, si controlla comunque la sintassi con lo strumento del programma (nginx -t).

Esempio 5: solo "sono diversi?" e il codice di uscita

$ diff -q frutta1.txt frutta2.txt
I file frutta1.txt e frutta2.txt sono diversi
$ echo $?
1
$ diff -q frutta1.txt frutta1.txt
$ echo $?
0
$ diff frutta1.txt nonesiste.txt
diff: nonesiste.txt: File o directory non esistente
$ echo $?
2

Con -q (brief) diff dice solo se i file sono diversi, senza elencare le differenze: utile per file grandi o per confronti in massa. I tre codici di uscita sono 0 (uguali), 1 (diversi) e 2 (errore). Attenzione: 1 non è un errore, ma il risultato normale di un confronto che ha trovato differenze.

Esempio 6: ignorare spazi, maiuscole e righe vuote

$ printf 'a b\nc\n' > w1
$ printf 'a  b\nc\n' > w2
$ diff w1 w2
1c1
< a b
---
> a  b
$ diff -b w1 w2
$ diff -w w1 w2
$ printf 'Ciao\n' > i1; printf 'ciao\n' > i2
$ diff -i i1 i2

A volte differenze di formattazione non interessano. -b ignora le differenze nella quantità di spazi (uno o due contano uguale), -w ignora tutti gli spazi e le tabulazioni, -i ignora la differenza tra maiuscole e minuscole e -B ignora le righe vuote aggiunte o tolte. Nell'esempio, i confronti con -b, -w e -i non stampano niente: per diff i file sono uguali (codice di uscita 0).

Esempio 7: confrontare due cartelle

$ diff -rq sito-a sito-b
I file sito-a/index.html e sito-b/index.html sono diversi
Solo in sito-b: nuovo.txt
Solo in sito-a: vecchio.txt
$ diff -r sito-a sito-b
diff -r sito-a/index.html sito-b/index.html
1a2
> <p>Novita</p>
Solo in sito-b: nuovo.txt
Solo in sito-a: vecchio.txt
$ diff -rq -x '*.log' -x cache sito-a sito-b

Con -r (recursive) diff scende in tutte le sottocartelle e confronta i file con lo stesso nome. Con -rq si ottiene la lista dei file diversi e di quelli presenti in una sola delle due cartelle: è il modo migliore per verificare che una copia, per esempio quella fatta con rsync o tar, sia identica all'originale. -x modello esclude file e cartelle (come --exclude di rsync). Senza -q, -r mostra anche le differenze interne di ogni file.

Esempio 8: creare e applicare una patch

$ diff -ruN sito-a sito-b > modifiche.patch
$ cd sito-a
$ patch -p1 --dry-run < ../modifiche.patch
checking file index.html
checking file nuovo.txt
checking file vecchio.txt
$ patch -p1 < ../modifiche.patch
patching file index.html
patching file nuovo.txt
patching file vecchio.txt
$ patch -R -p1 < ../modifiche.patch

Una patch è l'output di diff -u salvato in un file, e il comando patch lo applica per ripetere le stesse modifiche su un'altra copia dei file. Con -r si copre un'intera cartella, e -N (new file) fa includere nella patch anche i file nuovi o cancellati (altrimenti compare solo "Solo in…"). -p1 dice a patch di togliere il primo livello di cartelle dai nomi (sito-a/, sito-b/), e si lancia dentro la cartella da modificare. Con --dry-run si prova senza modificare niente, e con -R (reverse) si annulla una patch già applicata.

Esempio 9: confrontare l'output di due comandi

$ diff <(sort nginx-vecchio.conf) <(sort nginx-nuovo.conf)
1c1
<     index index.html;
---
>     listen 443 ssl;
4c4
<     server_name esempio.org;
---
>     server_name esempio.org www.esempio.org;
$ ls sito-a > l1; ls sito-b > l2; diff l1 l2
3c3
< vecchio.txt
---
> nuovo.txt
$ echo ciao | diff - frutta1.txt

La sintassi <(comando) (process substitution, di bash) permette di confrontare direttamente l'output di due comandi, senza creare file temporanei: qui i due file sono ordinati con sort prima del confronto, utile quando l'ordine delle righe non conta. Con il trattino - al posto del nome di un file, diff legge dallo standard input. Un uso tipico su un server: confrontare i pacchetti installati su due macchine, con diff <(ssh web1 'dpkg -l | awk "{print \$2}"') <(ssh web2 'dpkg -l | awk "{print \$2}"') (vedi gli articoli su ssh e apt).

Esempio 10: colori e confronto di file binari

$ diff --color=auto -u frutta1.txt frutta2.txt
$ diff immagine1.png immagine2.png
I file binari immagine1.png e immagine2.png sono diversi
$ cmp frutta1.txt frutta2.txt
frutta1.txt frutta2.txt differ: char 8, line 2
$ cmp immagine1.png immagine2.png && echo "identiche"
$ sha256sum immagine1.png immagine2.png

Con --color le righe aggiunte sono verdi e quelle tolte rosse, e l'opzione si può rendere permanente con un alias (alias diff='diff --color=auto'). Per i file binari, diff si limita a dire che sono diversi. Per i confronti byte per byte si usa cmp, che indica la posizione del primo byte diverso (con -s non stampa nulla e restituisce solo il codice di uscita); per sapere se due file sono identici basta confrontare le loro impronte con sha256sum.

Esempio 11: uso negli script

#!/bin/bash
FILE="/etc/nginx/nginx.conf"
BASE="/var/backups/nginx.conf.base"
OUT=$(mktemp)

diff -u "$BASE" "$FILE" > "$OUT"
case $? in
    0) echo "Nessuna modifica a $FILE" ;;
    1) echo "ATTENZIONE: $FILE è cambiato:"
       cat "$OUT" ;;
    *) echo "Errore nel confronto" >&2
       rm -f "$OUT"
       exit 2 ;;
esac

rm -f "$OUT"

Lo script confronta un file con una copia di riferimento e si comporta in modo diverso nei tre casi di diff. Il codice di uscita va letto subito dopo il comando ($? cambia a ogni comando). Se lo script usa set -e (esci al primo errore), un confronto con differenze, che esce con 1, lo interromperebbe: in quel caso si scrive diff ... || true o si controlla con un if. Lo stesso controllo, programmato con cron, avvisa quando un file importante cambia senza che tu l'abbia deciso.

Opzioni utili

  • -u / -U N: formato unificato (con N righe di contesto); -c: formato "contestuale", più vecchio.
  • -y / -W N / --suppress-common-lines: affiancato, larghezza, solo differenze.
  • -q (--brief): dice solo se i file sono diversi.
  • -r: ricorsivo; -N: tratta i file mancanti come vuoti; -x modello: esclude; -s: segnala anche i file uguali.
  • -b / -w / -i / -B: ignora differenze di spazi, spazi, maiuscole, righe vuote.
  • --color[=auto]: colori; --label nome: sostituisce i nomi nell'intestazione.
  • -a: tratta i file come testo anche se sembrano binari.
  • cmp: confronto byte per byte; comm: confronta due file ordinati e mostra righe comuni e righe esclusive; sdiff: confronto interattivo.
  • Altri strumenti: git diff (anche per file fuori da un repository, con --no-index), vimdiff e meld (grafico).
  • patch -pN / --dry-run / -R: applica, prova, annulla una patch.

Attenzione a

  • L'ordine dei file conta: diff vecchio nuovo e diff nuovo vecchio danno risultati opposti (le righe con < e >, o con - e +, si scambiano). Il primo è sempre quello "di partenza".
  • Il codice di uscita 1 non è un errore: significa "ci sono differenze". Negli script con set -e o con && può interrompere il lavoro quando non dovrebbe. L'errore vero è il 2.
  • Confronta il contenuto, non i permessi: due file con lo stesso testo ma permessi, proprietario o data diversi risultano uguali. Per quelli si usa ls -l o stat; per le cartelle, rsync -avn (la simulazione) con -i mostra anche le differenze nei metadati.
  • Fine riga e ultimo a capo: un file con fine riga di Windows (\r\n) risulta diverso su ogni riga, e un'unica riga finale senza a capo viene segnalata con un messaggio \ No newline at end of file nel testo del confronto. Con --strip-trailing-cr si ignora la differenza dei fine riga, e con cat -A si vedono i caratteri nascosti.
  • File segreti: diff stampa a schermo il contenuto delle righe diverse. Se confronti file con password o chiavi (.env, configurazioni con credenziali), usa -q, o fai attenzione a dove finisce l'output (cronologia dello schermo, registri, messaggi inviati a qualcuno).
  • Le intestazioni di -u contengono le date: se confronti due file creati nello stesso istante le date non servono, ma in una patch da pubblicare conviene sostituirle con --label, per non rivelare nomi di percorsi e orari.
  • Directory contro file: diff cartella file cerca nella cartella un file con lo stesso nome del secondo argomento, e dà errore se non lo trova. Se volevi confrontare due cartelle, verifica che entrambi gli argomenti lo siano.
  • File grandi e righe lunghissime: diff carica molto in memoria e con file di centinaia di megabyte è lento; i file con righe molto lunghe (come un JSON su una sola riga) danno differenze illeggibili. In quel caso conviene formattare prima i file (per esempio con jq . per un JSON) e poi confrontarli.
  • Non applicare patch che non conosci: una patch può modificare qualunque file, anche in percorsi sensibili. Leggila prima, prova con --dry-run e controlla i nomi dei file coinvolti. Un -p sbagliato può far applicare la modifica al file sbagliato o fallire.
  • Per due file ordinati usa comm, per i file binari cmp, e per gli elenchi di nomi che non hanno un ordine importante, ordina prima con sort (vedi l'esempio 9).

Riepilogo

ComandoCosa fa
diff file1 file2Elenca le differenze (formato normale)
diff -u file1 file2Formato unificato, con contesto (il più leggibile)
diff -y -W 160 file1 file2Affianca i due file
diff -q file1 file2Dice solo se i file sono diversi
diff -w file1 file2Ignora le differenze di spazi
diff -i file1 file2Ignora maiuscole e minuscole
diff -rq dir1 dir2Confronta due cartelle, elenca i file diversi
diff -ruN dir1 dir2 > mod.patchCrea una patch per un'intera cartella
patch -p1 < mod.patchApplica una patch
patch -R -p1 < mod.patchAnnulla una patch
diff <(comando1) <(comando2)Confronta l'output di due comandi
cmp file1 file2Confronto byte per byte (anche binari)
echo $?Codice di uscita: 0 uguali, 1 diversi, 2 errore