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>}