filecmp - Confrontare File
Scopo: Confronta file e directory nel filesystem.
Il modulo filecmp include funzioni ed una classe per confrontare i file e le directory nel filesystem.
Dati di Esempio
Gli esempi per questo articolo utilizzano una serie di file di prova creati con lo script filecmp_mkexamples.py
.
# filecmp_mkexamples.py
import os
def mkfile(filename, body=None):
with open(filename, 'w') as f:
f.write(body or filename)
return
def make_example_dir(top):
if not os.path.exists(top):
os.mkdir(top)
curdir = os.getcwd()
os.chdir(top)
os.mkdir('dir1')
os.mkdir('dir2')
mkfile('dir1/file_solo_in_dir1')
mkfile('dir2/file_solo_in_dir2')
os.mkdir('dir1/dir_solo_in_dir1')
os.mkdir('dir2/dir_solo_in_dir2')
os.mkdir('dir1/dir_comune')
os.mkdir('dir2/dir_comune')
mkfile('dir1/file_comune', 'questo file è lo stesso')
mkfile('dir2/file_comune', 'questo file è lo stesso')
mkfile('dir1/non_lo_stesso')
mkfile('dir2/non_lo_stesso')
mkfile('dir1/file_in_dir1', 'Questo è un file in dir1')
os.mkdir('dir2/file_in_dir1')
os.chdir(curdir)
return
if __name__ == '__main__':
os.chdir(os.path.dirname(__file__) or os.getcwd())
make_example_dir('esempio')
make_example_dir('esempio/dir1/dir_comune')
make_example_dir('esempio/dir2/dir_comune')
L'esecuzione dello script produce un alberatura di file sotto la directory esempio
.
$ find esempio esempio esempio/dir1 esempio/dir1/dir_comune esempio/dir1/dir_comune/dir1 esempio/dir1/dir_comune/dir1/dir_comune esempio/dir1/dir_comune/dir1/dir_solo_in_dir1 esempio/dir1/dir_comune/dir1/file_solo_in_dir1 esempio/dir1/dir_comune/dir1/file_comune esempio/dir1/dir_comune/dir1/non_lo_stesso esempio/dir1/dir_comune/dir1/file_in_dir1 esempio/dir1/dir_comune/dir2 esempio/dir1/dir_comune/dir2/dir_comune esempio/dir1/dir_comune/dir2/file_solo_in_dir2 esempio/dir1/dir_comune/dir2/dir_solo_in_dir2 esempio/dir1/dir_comune/dir2/file_comune esempio/dir1/dir_comune/dir2/non_lo_stesso esempio/dir1/dir_comune/dir2/file_in_dir1 esempio/dir1/dir_solo_in_dir1 esempio/dir1/file_solo_in_dir1 esempio/dir1/file_comune esempio/dir1/non_lo_stesso esempio/dir1/file_in_dir1 esempio/dir2 esempio/dir2/dir_comune esempio/dir2/dir_comune/dir1 esempio/dir2/dir_comune/dir1/dir_comune esempio/dir2/dir_comune/dir1/dir_solo_in_dir1 esempio/dir2/dir_comune/dir1/file_solo_in_dir1 esempio/dir2/dir_comune/dir1/file_comune esempio/dir2/dir_comune/dir1/non_lo_stesso esempio/dir2/dir_comune/dir1/file_in_dir1 esempio/dir2/dir_comune/dir2 esempio/dir2/dir_comune/dir2/dir_comune esempio/dir2/dir_comune/dir2/file_solo_in_dir2 esempio/dir2/dir_comune/dir2/dir_solo_in_dir2 esempio/dir2/dir_comune/dir2/file_comune esempio/dir2/dir_comune/dir2/non_lo_stesso esempio/dir2/dir_comune/dir2/file_in_dir1 esempio/dir2/file_solo_in_dir2 esempio/dir2/dir_solo_in_dir2 esempio/dir2/file_comune esempio/dir2/non_lo_stesso esempio/dir2/file_in_dir1
La stessa struttura di directory è ripetuta una volta nelle sottodirectory dir_comune
per fornire delle interessanti opzioni di confronto ricorsivo.
Confrontare File
cmp()
confronta due file nel filesystem.
# filecmp_cmp.py
import filecmp
print('file_comune :', end=' ')
print(filecmp.cmp('esempio/dir1/file_comune',
'esempio/dir2/file_comune'),
end=' ')
print(filecmp.cmp('esempio/dir1/file_comune',
'esempio/dir2/file_comune',
shallow=False))
print('non_lo_stesso:', end=' ')
print(filecmp.cmp('esempio/dir1/non_lo_stesso',
'esempio/dir2/non_lo_stesso'),
end=' ')
print(filecmp.cmp('esempio/dir1/non_lo_stesso',
'esempio/dir2/non_lo_stesso',
shallow=False))
print('identici :', end=' ')
print(filecmp.cmp('esempio/dir1/file_solo_in_dir1',
'esempio/dir1/file_solo_in_dir1'),
end=' ')
print(filecmp.cmp('esempio/dir1/file_solo_in_dir1',
'esempio/dir1/file_solo_in_dir1',
shallow=False))
L'argomento shallow
indica a cmp()
di cercare nel contenuto del file, oltre ai suoi metadati. Il comportamento predefinito è di eseguire un confronto "superficiale" usando le informazioni disponibili da os.stat()
. Se i risultati sono gli stessi, i file sono considerati gli stessi, quindi file della stessa dimensione creati allo stesso orario sono riportati come uguali, anche se il contenuto differisce. Quando shallow
è False
, il contenuto del file è sempre oggetto di confronto.
$ python3 filecmp_cmp.py file_comune : True True non_lo_stesso: True False identici : True True
Per confrontare un inseme di file in due directory senza ricorsione si usi cmpfiles()
. Gli argomenti sono i nomi delle directory ed una lista di file da verificare nelle due locazioni. La lista dei file comuni passata dovrebbe contenere solo nomi di file e gli stessi devono essere presenti in entrambe le locazioni. Il prossimo esempio mostra un semplice modo per costruire la lista comune. Il confronto riceve anche l'argomento shallow
, proprio come per cmp()
.
# filecmp_cmpfiles.py
import filecmp
import os
# Determina gli elementi che esistono in entrambe le directory
d1_contents = set(os.listdir('esempio/dir1'))
d2_contents = set(os.listdir('esempio/dir2'))
common = list(d1_contents & d2_contents)
common_files = [
f
for f in common
if os.path.isfile(os.path.join('esempio/dir1', f))
]
print('File comuni:', common_files)
# Confronta le directory
match, mismatch, errors = filecmp.cmpfiles(
'esempio/dir1',
'esempio/dir2',
common_files,
)
print('Corrispondenze :', match)
print('Mancate corrispondenze:', mismatch)
print('Errori :', errors)
cmpfiles()
ritorna tre liste di nomi di file, quelli che hanno trovato corrispondenza, quelli senza corrispondenza e file per i quali non è stato possibile effettuare il confronto (a causa di problemi di permessi o qualsiasi altra ragione).
$ python3 filecmp_cmpfiles.py File comuni: ['non_lo_stesso', 'file_in_dir1', 'file_comune'] Corrispondenze : ['non_lo_stesso', 'file_comune'] Mancate corrispondenze: ['file_in_dir1'] Errori : []
Confrontare Directory
Le funzioni descritte precedentemente sono indicate per confronti relativamente semplici. Per confronti ricorsivi di grandi alberature di directory o per analisi più complete, la classe dircmp
è più utile. Nel suo caso d'uso più semplice, report()
stampa il risultato del confronto di due directory.
# filecmp_dircmp_report.py
import filecmp
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
dc.report()
Il risultato è una evidenza in testo semplice che mostra i risultati del solo contenuto delle due directory passate, senza ricorsione. In questo caso, il file non_lo_stesso
è stato interpretato come identico in quanto il contenuto non è stato confrontato. Non c'è modo di effettuare confronti con dircmp
nello stesso modo utilizzato da cmp()
con i file.
$ python3 filecmp_dircmp_report.py diff esempio/dir1 esempio/dir2 Only in esempio/dir1 : ['dir_solo_in_dir1', 'file_solo_in_dir1'] Only in esempio/dir2 : ['dir_solo_in_dir2', 'file_solo_in_dir2'] Identical files : ['file_comune', 'non_lo_stesso'] Common subdirectories : ['dir_comune'] Common funny cases : ['file_in_dir1']
Per maggiori dettagli, ed un confronto ricorsivo, si usi report_full_closure()
:
# filecmp_dircmp_report_full_closure.py
import filecmp
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
dc.report_full_closure()
Il risultato comprende confronti con tutte le directory parallele.
$ python3 filecmp_dircmp_report_full_closure.py diff esempio/dir1 esempio/dir2 Only in esempio/dir1 : ['dir_solo_in_dir1', 'file_solo_in_dir1'] Only in esempio/dir2 : ['dir_solo_in_dir2', 'file_solo_in_dir2'] Identical files : ['file_comune', 'non_lo_stesso'] Common subdirectories : ['dir_comune'] Common funny cases : ['file_in_dir1'] diff esempio/dir1/dir_comune esempio/dir2/dir_comune Common subdirectories : ['dir1', 'dir2'] diff esempio/dir1/dir_comune/dir1 esempio/dir2/dir_comune/dir1 Identical files : ['file_comune', 'file_in_dir1', 'file_solo_in_dir1', 'non_lo_stesso'] Common subdirectories : ['dir_comune', 'dir_solo_in_dir1'] diff esempio/dir1/dir_comune/dir1/dir_solo_in_dir1 esempio/dir2/dir_comune/dir1/dir_solo_in_dir1 diff esempio/dir1/dir_comune/dir1/dir_comune esempio/dir2/dir_comune/dir1/dir_comune diff esempio/dir1/dir_comune/dir2 esempio/dir2/dir_comune/dir2 Identical files : ['file_comune', 'file_solo_in_dir2', 'non_lo_stesso'] Common subdirectories : ['dir_comune', 'dir_solo_in_dir2', 'file_in_dir1'] diff esempio/dir1/dir_comune/dir2/dir_solo_in_dir2 esempio/dir2/dir_comune/dir2/dir_solo_in_dir2 diff esempio/dir1/dir_comune/dir2/file_in_dir1 esempio/dir2/dir_comune/dir2/file_in_dir1 diff esempio/dir1/dir_comune/dir2/dir_comune esempio/dir2/dir_comune/dir2/dir_comune
Usare le Differenze in un Programma
Oltre alla produzione di risultati stampati, dircmp
calcola liste di file che possono essere usate direttamente nei programmi. Ognuno dei seguenti attributi è calcolato solo quando richiesto, quindi creare istanze di dircmp
non comporta un lavoro non necessario per i dati non utilizzati.
# filecmp_dircmp_list.py
import filecmp
import pprint
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print('Sinistra:')
pprint.pprint(dc.left_list)
print('\nDestra :')
pprint.pprint(dc.right_list)
I file e le sottodirectory contenuti nelle directory oggetto di confronto sono elencati nelle liste left_list
e right_list
.
$ python3 filecmp_dircmp_list.py Sinistra: ['dir_comune', 'dir_solo_in_dir1', 'file_comune', 'file_in_dir1', 'file_solo_in_dir1', 'non_lo_stesso'] Destra : ['dir_comune', 'dir_solo_in_dir2', 'file_comune', 'file_in_dir1', 'file_solo_in_dir2', 'non_lo_stesso']
I dati in entrata possono essere filtrati passando al costruttore un elenco di nomi da ignorare. Nella modalità predefinita i nomi RCS
, CVS
e tags
sono ignorati.
# filecmp_dircmp_list_filter.py
import filecmp
import pprint
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2',
ignore=['file_comune'])
print('Sinistra:')
pprint.pprint(dc.left_list)
print('\nDestra :')
pprint.pprint(dc.right_list)
In questo caso, il file file_comune
non è stato incluso nella lista dei file da confrontare.
$ python3 filecmp_dircmp_list_filter.py Sinistra: ['dir_comune', 'dir_solo_in_dir1', 'file_in_dir1', 'file_solo_in_dir1', 'non_lo_stesso'] Destra : ['dir_comune', 'dir_solo_in_dir2', 'file_in_dir1', 'file_solo_in_dir2', 'non_lo_stesso']
I nomi dei file comuni ad entrambe le directory confrontate sono salvati in common
e i file univoci in ciascuna directory sono elencati in left_only
e right_only
.
# filecmp_dircmp_membership.py
import filecmp
import pprint
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print('Comuni :')
pprint.pprint(dc.common)
print('\nSinistra:')
pprint.pprint(dc.left_only)
print('\nDestra :')
pprint.pprint(dc.right_only)
La directory di "sinistra" è il primo argomento per dircmp()
e quella di "destra" il secondo.
$ python3 filecmp_dircmp_membership.py Comuni : ['file_comune', 'non_lo_stesso', 'dir_comune', 'file_in_dir1'] Sinistra: ['dir_solo_in_dir1', 'file_solo_in_dir1'] Destra : ['file_solo_in_dir2', 'dir_solo_in_dir2']
Il membri comuni possono essere successivamente divisi in file, directory ed elementi "funny" (qualunque cosa che sia di tipo diverso nelle due directory o quando si ottiene un errore da os.stat()
).
# filecmp_dircmp_common.py
import filecmp
import pprint
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print('Comuni:')
pprint.pprint(dc.common)
print('\nDirectory:')
pprint.pprint(dc.common_dirs)
print('\nFile:')
pprint.pprint(dc.common_files)
print('\nFunny:')
pprint.pprint(dc.common_funny)
Nei dati di esempio, l'elemento chiamato file_in_dir1
è un file in una directory ed una sottodirectory nell'altra. quindi viene inserito nella lista "
$ python3 filecmp_dircmp_common.py Comuni: ['file_in_dir1', 'dir_comune', 'file_comune', 'non_lo_stesso'] Directory: ['dir_comune'] File: ['file_comune', 'non_lo_stesso'] Funny: ['file_in_dir1']
Le differenze tra file sono destrutturate allo stesso modo.
# filecmp_dircmp_diff.py
import filecmp
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print('Uguali :', dc.same_files)
print('Diversi:', dc.diff_files)
print('Funny :', dc.funny_files)
Il file non_lo_stesso
viene confrontato solo tramite os.stat()
, ed il suo contenuto non viene esaminato, quindi viene compreso nella lista dei file uguali (same_files
).
$ python3 filecmp_dircmp_diff.py Uguali : ['file_comune', 'non_lo_stesso'] Diversi: [] Funny : []
In ultimo, vengono salvate anche le sottodirectory in modo da consentire una facile comparazione ricorsiva.
# filecmp_dircmp_subdirs.py
import filecmp
dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print('Sottodirectory:')
print(dc.subdirs)
L'attributo subdirs
è un dizionario che mappa il nome della directory con i nuovi oggetti dircmp
.
$ python3 filecmp_dircmp_subdirs.py Sottodirectory: {'dir_comune': <filecmp.dircmp object at 0x7f9042446160>}