[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [translators] [docinfo] [indice analitico] [volume] [parte]
Spesso esiste la necessità di confrontare il contenuto tra dei file per verificare se esistono delle differenze, ma soprattutto per conoscere quali sono, quando queste non sono troppe. Se le differenze tra i due file sono in numero ragionevolmente contenuto, si può generarne un rapporto, in modo da poter ottenere uno dei due file a partire dall'altro, assieme a tale elenco di variazioni.
Questo rapporto sulle differenze, definito prevalentemente patch, si applica a un file, o a una serie di file, per ottenere altrettanti file aggiornati.
Esistono tanti modi di costruire un file di differenze. Si distinguono in particolare due situazioni: i file di testo e gli altri. Si può comprendere che in un file di testo, tipicamente un sorgente di un programma, i cambiamenti avvengano a livello di righe, nel senso che se ne possono aggiungere, togliere e modificare. In un file binario invece, non avendo il riferimento delle righe, il problema è più complesso. La gestione delle differenze tra i file riguarda prevalentemente i file di testo normale, cosa di cui si vuole trattare in questo capitolo.
Il programma più importante per analizzare le differenze tra due file di testo è diff. (1) Può funzionare con diverse modalità, per determinare semplicemente se una coppia di file è identica o meno, oppure per indicare le differenze che ci sono tra i due, con maggiore o minore dettaglio di informazioni al riguardo. La sintassi sintetica di questo programma è molto semplice.
diff [opzioni] file_1 file_2 |
Il risultato del confronto dei file viene emesso attraverso lo standard output.
Quando si confrontano file di testo, può darsi che alcuni tipi di differenze non siano da considerare, come per esempio l'aggiunta di spazi alla fine di una riga, o l'inserzione di righe vuote addizionali. Inoltre, si può desiderare di conoscere semplicemente se esiste una qualche differenza, senza entrare troppo nel dettaglio. Questa sensibilità alle differenze viene definita attraverso l'uso di opzioni apposite. Le più importanti sono elencate nella tabella successiva.
Con l'opzione -i le due righe seguenti sono considerate equivalenti:
|
Con l'opzione -b, le due righe seguenti sono considerate equivalenti:
|
Con l'opzione -w, le due righe seguenti sono equivalenti:
|
Prima di iniziare un confronto tra due file, diff verifica che si tratti di file di testo in base al contenuto di alcune righe iniziali. Se diff incontra il carattere <NUL>, a meno che siano state usate opzioni particolari in senso contrario, assume che si tratti di un file binario e verifica semplicemente se i file sono identici.
|
Il funzionamento normale di diff prevede l'emissione attraverso lo standard output dell'indicazione delle sole differenze tra i file, secondo il formato seguente:
comando < riga_primo_file < riga_primo_file < ... --- > riga_secondo_file > riga_secondo_file > ... |
In questo tipo di notazione, è il «comando» a stabilire l'operazione da compiere. Il comando si compone di tre parti: il numero di una riga, o di un intervallo di righe del primo file; una lettera che definisce l'operazione da compiere; il numero di una riga, o di un intervallo di righe del secondo file.
righe_file_1azionerighe_file_2 |
Si distinguono le tre azioni seguenti.
Aggiunta
posizione_file_1arighe_file_2 |
Indica che per ottenere il secondo file a partire dal primo, occorre aggiungere a questo le righe indicate a destra dell'azione, dopo la posizione indicata a sinistra. Per esempio, 5a6,8 significa che per ottenere il secondo file occorre aggiungere al primo le righe dalla sesta all'ottava del secondo, dopo la quinta riga del primo file.
Sostituzione
righe_file_1crighe_file_2 |
Indica di sostituire le righe del primo file, indicate a sinistra dell'azione, con quelle del secondo file indicate a destra.
Cancellazione
righe_file_1dposizione_file_2 |
Indica che per ottenere il secondo file a partire dal primo, occorre eliminare dal primo le righe indicate a sinistra dell'azione. L'indicazione della posizione del secondo file serve solo per completezza, a specificare il punto in cui tali righe mancano. In pratica, l'azione d è l'inverso dell'azione a.
Quando si vogliono distribuire file di differenze (o delle patch, se si preferisce il termine) per consentire ad altri di ottenere degli aggiornamenti da un file di partenza, è sconsigliabile l'utilizzo di questo formato, benché si tratti di quello predefinito per diff, secondo lo standard POSIX. |
Per verificare in pratica il funzionamento di diff in modo da ottenere l'indicazione delle differenze tra due file senza informazioni sul contesto, viene proposto il confronto tra i due file seguenti:
|
|
I nomi dei due file siano rispettivamente: primo
e secondo
.
$
diff primo secondo
[Invio]
1,2c1,2 < Chi va piano, < va sano --- > chi va piano, > va sano |
In pratica, le prime due righe del primo file vanno sostituite con le prime due del secondo, mentre la terza riga è la stessa in entrambi i file.
$
diff -i primo secondo
[Invio]
2c2 < va sano --- > va sano |
In questo caso, utilizzando l'opzione -i, si vogliono ignorare le differenze tra lettere maiuscole e minuscole, pertanto risulta diversa solo la seconda riga.
$
diff -b primo secondo
[Invio]
1c1 < Chi va piano, --- > chi va piano, |
In questo caso, utilizzando l'opzione -b si vogliono ignorare le differenze dovute a un uso differente delle spaziature tra le parole, pertanto risulta diversa solo la prima riga.
Il funzionamento normale di diff prevede l'emissione attraverso lo standard output dell'indicazione delle sole differenze tra i file, ma ciò è generalmente poco adatto alla distribuzione di file di differenze. Per questo è preferibile utilizzare un formato che, assieme alle modifiche, inserisca anche alcune righe di riferimento aggiuntive. In questo modo, il programma che deve applicare le modifiche, può agire anche se il contenuto del file sul quale vengono applicate ha subito piccoli spostamenti. Si ottiene un formato contestuale standard quando si utilizza l'opzione seguente:
-c | -C righe | --context[=righe] |
Se viene indicato il numero di righe, si intende che venga utilizzato almeno quel numero di righe di riferimento contestuale. Se questo valore non viene indicato, si intende che siano tre. Il minimo perché il programma patch possa eseguire il suo compito è di due righe contestuali.
Il risultato di una comparazione contestuale standard è preceduto da due righe di intestazione contenenti l'indicazione dei due file.
*** file_1 data_di_modifica_del_primo_file --- file_2 data_di_modifica_del_secondo_file |
Successivamente appaiono i blocchi delle differenze, strutturati nel modo seguente:
*************** *** righe_primo_file **** riga_primo_file riga_primo_file ... --- righe_corrispondenti_secondo_file ---- riga_secondo_file riga_secondo_file ... |
Si deve osservare che le righe vengono indicate a partire dalla terza colonna, lasciando cioè due spazi dall'inizio. La prima colonna viene utilizzata per indicare il ruolo particolare di quella riga:
se non appare alcun simbolo, si tratta di una riga di contesto che non risulta modificata;
il punto esclamativo (!) rappresenta un cambiamento tra i due file;
il segno + rappresenta una riga aggiunta nel secondo file che nel primo non esiste;
il segno - rappresenta una riga nel primo file che nel secondo risulta cancellata.
Per verificare in pratica il funzionamento di diff in modo da utilizzare il formato contestuale standard, viene proposto il confronto tra i due file seguenti:
|
|
I nomi dei due file siano rispettivamente: primo
e secondo
.
$
diff -c primo secondo
[Invio]
*** primo Tue Mar 3 08:12:30 1998 --- secondo Wed Mar 4 11:16:32 1998 *************** *** 1,3 **** ! Chi va piano, ! va sano e va lontano --- 1,6 ---- ! Chi va forte, ! va alla morte; ! ! chi va piano, ! va sano e va lontano |
In breve, le prime tre righe del primo file vanno sostituite con le prime sei del secondo e l'unica riga in comune è l'ultima.
$
diff -c -i primo secondo
[Invio]
*** primo Tue Mar 3 08:12:30 1998 --- secondo Wed Mar 4 11:16:32 1998 *************** *** 1,3 **** Chi va piano, ! va sano e va lontano --- 1,6 ---- + Chi va forte, + va alla morte; + chi va piano, ! va sano e va lontano |
In questo caso, vanno aggiunte le prime tre righe del secondo file, quindi si incontra una riga uguale, dal momento che non contano le differenze tra lettere maiuscole e minuscole, infine viene sostituita una riga a causa della spaziatura orizzontale differente.
$
diff -b -i -c primo secondo
[Invio]
*** primo Tue Mar 3 08:12:30 1998 --- secondo Wed Mar 4 11:16:32 1998 *************** *** 1,3 **** --- 1,6 ---- + Chi va forte, + va alla morte; + chi va piano, va sano e va lontano |
In questo caso, avendo indicato che non contano le differenze dovute alla diversa spaziatura orizzontale e all'uso delle maiuscole, le ultime tre righe del secondo file corrispondono esattamente al primo file. In questo modo, tali righe non sono state indicate nella parte che riguarda il primo file.
A fianco del formato contestuale standard, si pone un altro tipo di indicazione delle modifiche, definito «unificato», che ha il vantaggio di essere più compatto, ma anche lo svantaggio di essere disponibile solo negli strumenti GNU. Per selezionare questo tipo di risultato si utilizza una delle opzioni seguenti.
-u | -U righe | --unified[=righe] |
Se viene indicato il numero di righe, si intende che venga utilizzato almeno quel numero di righe di riferimento contestuale. Se questo valore non viene indicato, si intende che siano tre. Il minimo perché il programma patch possa eseguire il suo compito è di due righe contestuali.
Il risultato di una comparazione contestuale unificata è preceduto da due righe di intestazione contenenti l'indicazione dei due file.
--- file_1 data_di_modifica_del_primo_file +++ file_2 data_di_modifica_del_secondo_file |
Successivamente appaiono i blocchi delle differenze, strutturati nel modo seguente:
@@ -righe_primo_file +righe_secondo_file @@ riga_di_uno_dei_file riga_di_uno_dei_file ... |
In modo simile al caso del formato contestuale standard, le righe sono riportate a partire dalla seconda colonna, lasciando il primo carattere libero per indicare l'operazione da compiere:
le righe comuni a entrambi i file iniziano con un carattere spazio (<SP>);
il segno + rappresenta l'inserimento di una riga, in quel punto, rispetto al contenuto del primo file;
il segno - rappresenta una riga nel primo file che nel secondo risulta cancellata.
Per verificare in pratica il funzionamento di diff in modo da utilizzare il formato contestuale unificato, vengono proposti gli stessi esempi già visti nella sezione precedente:
$
diff -u primo secondo
[Invio]
--- primo Tue Mar 3 08:12:30 1998 +++ secondo Wed Mar 4 11:16:32 1998 @@ -1,3 +1,6 @@ -Chi va piano, -va sano +Chi va forte, +va alla morte; + +chi va piano, +va sano e va lontano |
In breve, le prime tre righe del primo file vanno sostituite con le prime sei del secondo e l'unica riga in comune è l'ultima.
$
diff -u -i primo secondo
[Invio]
--- primo Tue Mar 3 08:12:30 1998 +++ secondo Wed Mar 4 11:16:32 1998 @@ -1,3 +1,6 @@ +Chi va forte, +va alla morte; + Chi va piano, -va sano +va sano e va lontano |
In questo caso, vanno aggiunte le prime tre righe del secondo file, quindi si incontra una riga uguale, dal momento che non contano le differenze tra lettere maiuscole e minuscole, infine viene sostituita una riga a causa della spaziatura orizzontale differente.
$
diff -b -i -u primo secondo
[Invio]
--- primo Tue Mar 3 08:12:30 1998 +++ secondo Wed Mar 4 11:16:32 1998 @@ -1,3 +1,6 @@ +Chi va forte, +va alla morte; + Chi va piano, va sano e va lontano |
In questo caso, avendo indicato che non contano le differenze dovute alla diversa spaziatura orizzontale e all'uso delle maiuscole, le ultime tre righe del secondo file corrispondono esattamente al primo file. In questo modo, tali righe non sono state indicate nella parte che riguarda il primo file.
Il programma diff è in grado di generare un file di differenze unico, dal confronto di tutti i file di una directory con altrettanti file con lo stesso nome contenuti in un'altra. per ottenere questo, è sufficiente indicare il confronto di due directory, invece che di due file.
Se si desidera continuare l'analisi anche nelle sottodirectory successive, si può utilizzare l'opzione seguente:
-r | --recursive |
Si suppone che uno/
e due/
siano due sottodirectory della directory corrente nel momento in cui si esegue diff:
$
diff -u uno due
[Invio]
Ciò che si ottiene attraverso lo standard output è l'elenco delle modifiche dei vari file incontrati in entrambe le directory. Quello che segue è un estratto delle intestazioni in cui si vede in che modo sono indicati i file, assieme al loro percorso relativo:
|
Se all'esempio precedente si aggiunge l'opzione -r, diff attraversa anche le sottodirectory contenute in quelle indicate nella riga di comando:
$
diff -u -r uno due
[Invio]
Quando si prepara un file di differenze, è opportuno usare un po' di accortezza per facilitare il lavoro di chi poi deve applicare queste modifiche. È il caso di distinguere due situazioni fondamentali: le differenze riferite a un file singolo e quelle relative a un intero ramo di directory.
Per prima cosa occorre decidere il tipo di formato: quello predefinito non è molto comodo perché non contiene le informazioni sui nomi dei file, mentre quello contestuale unificato dovrebbe essere il migliore. Tuttavia, quando si devono produrre file di differenze da utilizzare con strumenti strettamente POSIX, ci si deve accontentare del formato contestuale standard.
In generale, è importante l'ordine in cui si indicano i file o le directory tra gli argomenti di diff: il primo dei due nomi rappresenta la situazione precedente, mentre il secondo quella nuova, ovvero l'aggiornamento verso cui si vuole andare. La situazione classica è quella in cui si modifica un file, ma prima di intervenire se ne salva una copia con la tipica estensione .orig
. Si osservi l'esempio seguente:
$
diff -u prova.txt.orig prova.txt > prova.diff
[Invio]
Il file prova.txt
è stato modificato, ma prima di farlo ne è stata salvata una copia con il nome prova.txt.orig
. Il comando genera un file di differenze tra prova.txt.orig
e prova.txt
.
Per realizzare un file di differenze di un ramo intero di directory, si interviene in modo simile: si fa una copia del ramo, si modifica quello che si vuole nei file del ramo che si intende debba contenere gli aggiornamenti, quindi si utilizza diff:
$
diff -u -r prova.orig prova > prova.diff
[Invio]
In questo caso, si intende fare riferimento al confronto tra le directory prova.orig/
e prova/
. Il file di differenze che si ottiene è unico per tutti i file che risultano modificati effettivamente.
Il programma più adatto per applicare delle modifiche è patch, (2) il quale di solito è in grado di determinare automaticamente il tipo di formato utilizzato e di saltare eventuali righe iniziali o finali aggiuntive. In pratica, con patch è possibile utilizzare file trasmessi come allegato nei messaggi di posta elettronica, senza doverli estrapolare.
patch [opzioni] < file_di_differenze |
patch utilizza generalmente lo standard input per ricevere i file di modifiche. Questi devono contenere l'indicazione del file da modificare, pertanto si possono utilizzare soltanto file di differenze in formato contestuale (compreso quello unificato).
In linea di massima, patch sovrascrive i file a cui si vogliono applicare delle modifiche, a meno che venga specificata un'opzione con la quale si richiede l'accantonamento di una copia della versione precedente. In questo caso, il file originale viene rinominato (in condizioni normali gli viene aggiunta l'estensione .orig
) e gli aggiornamenti vengono applicati a questo file ottenendone un altro con lo stesso nome di quello originale. patch cerca di applicare le modifiche anche quando il file di partenza non risulta perfettamente corrispondente a quanto indicato nel file di differenze. Se qualche blocco di modifiche non può essere applicato, questi vengono indicati in un file terminante con l'estensione .rej
.
La tabella 132.20 riepiloga brevemente le opzioni più comuni del programma patch GNU.
|
In condizioni normali, precisamente quando si dispone di file di differenze in formato contestuale (standard o unificato), non è necessario fornire a patch il nome del file su cui intervenire per applicare le modifiche, perché questo è indicato all'interno del file che le contiene. Tuttavia, il formato predefinito lo impone e in ogni caso può essere utile indicare precisamente a patch il nome del file su cui intervenire.
patch [opzioni] file_originale [file_di_differenze] |
Lo schema mostra semplicemente che è sufficiente accodare dopo le opzioni il nome del file originale al quale si vogliono applicare le modifiche. Queste possono essere contenute in un file indicato come argomento successivo, oppure fornito attraverso lo standard input, come si fa di solito. In alternativa, il file di differenze può anche essere indicato in modo esplicito attraverso l'opzione -i (ovvero --input).
Gli esempi seguenti sono equivalenti e servono ad applicare al file prova
le modifiche contenute nel file prova.diff
, sovrascrivendo il file prova
stesso:
$
patch prova prova.diff
[Invio]
$
patch prova < prova.diff
[Invio]
$
patch -i prova.diff prova
[Invio]
$
patch --input=prova.diff prova
[Invio]
In alcune circostanze, può essere utile, o necessario, definire esplicitamente di quale tipo sia il formato del file di differenze. A questo proposito si utilizzano alcune opzioni:
|
Un file di differenze che contiene informazioni su più coppie di file, deve essere di tipo contestuale (standard o unificato). Quando è stato generato facendo riferimento al contenuto di una directory, i nomi dei file presi in considerazione contengono l'indicazione di un percorso; pertanto, per riprodurre le modifiche in ambito locale, occorre tenere conto della posizione in cui cominciano a trovarsi i dati.
Inoltre, la directory corrente, nel momento in cui si avvia il programma patch, è importante per determinare quali siano i file a cui si devono applicare le modifiche.
|
Segue la descrizione di alcuni esempi.
$
patch -d ~/prove < prova.diff
[Invio]
Prima di applicare le modifiche contenute nel file di differenze prova.diff
, si sposta nella directory ~/prove/
.
$
patch -p0 < prova.diff
[Invio]
Applica le modifiche contenute nel file prova.diff
che presumibilmente contiene informazioni sui percorsi. L'opzione -p0 garantisce che a partire dalla directory corrente si articolano gli stessi percorsi che appaiono nel file di differenze.
$
patch -p1 < prova.diff
[Invio]
Applica le modifiche contenute nel file prova.diff
che presumibilmente contiene informazioni sui percorsi. L'opzione -p1 richiede l'eliminazione della prima barra obliqua nei percorsi, con l'intento presumibile di eliminare il primo livello di directory. Se all'interno del file di differenze si fa riferimento al file x/y/z/prova
, significa che le modifiche relative vanno applicate localmente al file y/z/prova
.
Nel caso in cui all'interno del file di differenze si facesse riferimento al file |
In modo analogo, nel caso in cui all'interno del file di differenze si facesse riferimento al file |
$
cd ~/linux
[Invio]
$
gzip -d -c ../usb-2.4.0-test2-pre2-for-2.2.16-v3.diff.gz
\
\| patch -p1
[Invio]
Questo è un esempio più complesso ma comune. Si tratta dell'applicazione del file di differenze usb-2.4.0-test2-pre2-for-2.2.16-v3.diff.gz
, che come si nota è anche compresso, contenuto nella directory personale dell'utente che compie l'operazione. Nell'esempio si intende che si tratti di modifiche relative ai sorgenti di un kernel Linux collocato a partire dalla directory ~/linux/
. In questo caso, il file di differenze inizia in questo modo:
|
Pertanto, essendo già la directory corrente corrispondente a linux/
, l'opzione -p1 risolve tutti i problemi.
In condizioni normali, patch sovrascrive i file a cui si applicano le modifiche. Per evitarlo è possibile definire precisamente il nome del file da generare, oppure si può gestire il sistema di mantenimento delle versioni precedenti, utilizzando in particolare l'opzione -b.
|
|
Segue la descrizione di alcuni esempi.
$
patch -o aggiornato < prova.diff
[Invio]
Applica le modifiche contenute nel file di differenze prova.diff
generando il file aggiornato
, senza toccare i file originali.
$
patch -b < prova.diff
[Invio]
Applica le modifiche contenute nel file di differenze prova.diff
, avendo cura di fare una copia di sicurezza dei file che aggiorna, prima di modificarli.
$
patch -b -z .vecchio < prova.diff
[Invio]
Applica le modifiche contenute nel file di differenze prova.diff
, avendo cura di fare una copia di sicurezza dei file che aggiorna utilizzando per questo l'estensione .vecchio
, prima di modificarli.
$
patch -b -V numbered < prova.diff
[Invio]
Applica le modifiche contenute nel file di differenze prova.diff
, avendo cura di fare una copia di sicurezza dei file che aggiorna utilizzando per questo un'estensione contenente un numero progressivo, prima di modificarli. La prima di queste copie di sicurezza ottiene l'estensione .~1~
, la seconda .~2~
e così di seguito.
patch è generalmente in grado di applicare delle modifiche anche a file che non sono perfettamente identici a quelli con cui sono stati costruiti i file di differenze. Tuttavia, ci sono situazioni in cui patch, da solo, non è in grado di poter prendere una decisione autonoma.
Può capitare che i file di modifiche vengano alterati involontariamente, per esempio a causa di una trasmissione attraverso la posta elettronica o per una modifica attraverso un programma per la gestione di file di testo. In questi casi potrebbero essere alterate le spaziature orizzontali attraverso una sostituzione dei caratteri di tabulazione con caratteri spazio, o viceversa. Un problema del genere può essere risolto utilizzando l'opzione -l.
-l | --ignore-white-space |
In questo modo, una sequenza di spazi qualunque equivale a un'altra sequenza di spazi, indipendentemente dal fatto che siano stati usati caratteri di tabulazione, o caratteri spazio veri e propri, ma anche indipendentemente dalla loro quantità.
Quando patch incontra dei problemi che non è in grado di risolvere da solo, richiede un intervento, ponendo delle domande all'utente. Se ciò accade, si può decidere di guidare patch nell'applicazione delle modifiche o di interrompere il procedimento.
Tutte le modifiche rigettate, vengono salvate in file terminanti con l'estensione .rej
, a meno che sia stabilito diversamente con l'opzione -r.
-r file_degli_errori | --reject-file=file_degli_errori |
Con questa opzione, in pratica, si stabilisce direttamente il nome del file che deve contenere le informazioni sulle modifiche che non sono state applicate per qualunque motivo.
Alla fine delle sezioni dedicate alla creazione di un file di differenze è stato chiarito che l'ordine in cui vanno indicati i file o le directory da confrontare, deve essere tale da avere prima l'oggetto che rappresenta la versione precedente e dopo quello che rappresenta la versione aggiornata.
Alle volte si hanno per le mani file di differenze ottenuti in modo inverso rispetto alle intenzioni reali, pertanto occorre richiedere a patch di adeguarvisi, se possibile.
La documentazione Info riguardo alla creazione, distribuzione e applicazione di modifiche, è molto dettagliata: info diff. Per il funzionamento di patch in particolare, conviene consultare la pagina di manuale patch(1).
Appunti di informatica libera 2006.07.01 --- Copyright © 2000-2006 Daniele Giacomini -- <daniele (ad) swlibero·org>
1) GNU diffutils GNU GPL
Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome differenze_tra_i_file.htm
[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [translators] [docinfo] [indice analitico]