filecmp - Confronta file

Scopo Confronta file e directory nel filesystem
Versione Python 2.1 e superiore

Dati di esempio

Gli esempi nella discussione di seguito usano un insieme di file di testo creati da filecmp_mkexamples.py.

import os

def mkfile(filename, body=None):
    f = open(filename, 'w')
    try:
        f.write(body or filename)
    finally:
        f.close()
    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')
$ ls -Rlast esempio
total 0
0 drwxr-xr-x  4 dhellmann  dhellmann  136 Apr 20 17:04 .
0 drwxr-xr-x  9 dhellmann  dhellmann  306 Apr 20 17:04 ..
0 drwxr-xr-x  8 dhellmann  dhellmann  272 Apr 20 17:04 dir1
0 drwxr-xr-x  8 dhellmann  dhellmann  272 Apr 20 17:04 dir2

esempio/dir1:
total 32
0 drwxr-xr-x  8 dhellmann  dhellmann  272 Apr 20 17:04 .
0 drwxr-xr-x  4 dhellmann  dhellmann  136 Apr 20 17:04 ..
0 drwxr-xr-x  2 dhellmann  dhellmann   68 Apr 20 17:04 dir_comune
8 -rw-r--r--  1 dhellmann  dhellmann   21 Apr 20 17:04 file_comune
0 drwxr-xr-x  2 dhellmann  dhellmann   68 Apr 20 17:04 dir_solo_in_dir1
8 -rw-r--r--  1 dhellmann  dhellmann   22 Apr 20 17:04 file_in_dir1
8 -rw-r--r--  1 dhellmann  dhellmann   22 Apr 20 17:04 file_solo_in_dir1
8 -rw-r--r--  1 dhellmann  dhellmann   17 Apr 20 17:04 non_lo_stesso

esempio/dir2:
total 24
0 drwxr-xr-x  8 dhellmann  dhellmann  272 Apr 20 17:04 .
0 drwxr-xr-x  4 dhellmann  dhellmann  136 Apr 20 17:04 ..
0 drwxr-xr-x  2 dhellmann  dhellmann   68 Apr 20 17:04 dir_comune
8 -rw-r--r--  1 dhellmann  dhellmann   21 Apr 20 17:04 file_comune
0 drwxr-xr-x  2 dhellmann  dhellmann   68 Apr 20 17:04 dir_solo_in_dir2
0 drwxr-xr-x  2 dhellmann  dhellmann   68 Apr 20 17:04 file_in_dir1
8 -rw-r--r--  1 dhellmann  dhellmann   22 Apr 20 17:04 file_solo_in_dir2
8 -rw-r--r--  1 dhellmann  dhellmann   17 Apr 20 17:04 non_lo_stesso

La stessa struttura di driectory viene ripetuta una volta sotto le directory "dir_comune" per offrire delle interessanti opzioni di confronto ricorsivo.

Confrontare file

Il modulo filecmp comprende delle funzioni ed una classe per confrontare file e directory nel filesystem. Se si devono confrontare due file si usa la funzione cmp().

import filecmp

print 'file_comune:',
print filecmp.cmp('esempio/dir1/file_comune',
                  'esempio/dir2/file_comune'),
print filecmp.cmp('esempio/dir1/file_comune',
                  'esempio/dir2/file_comune',
                  shallow=False)

print 'non_lo_stesso:',
print filecmp.cmp('esempio/dir1/non_lo_stesso',
                  'esempio/dir2/non_lo_stesso'),
print filecmp.cmp('esempio/dir1/non_lo_stesso',
                  'esempio/dir2/non_lo_stesso',
                  shallow=False)

print 'identici:',
print filecmp.cmp('esempio/dir1/file_solo_in_dir1',
                  'esempio/dir1/file_solo_in_dir1'),
print filecmp.cmp('esempio/dir1/file_solo_in_dir1',
                  'esempio/dir1/file_solo_in_dir1',
                  shallow=False)

Nel modo predefinito, cmp() accede solo alle informazioni disponibili da os.stat(). Il parametro shallow dice a cmp() se guardare anche al contenuto del file. Il modo predefinito è quello di effettuare un confronto shallow, senza guardare al contenuto del file. Notare che file della stessa dimensione creati allo stesso momento sembrano essere uguali se il loro contenuto non viene confrontato.

$ python filecmp_cmp.py
file_comuni: True True
non_lo_stesso: True False
identici: True True

Per confrontare un insieme di file in due directory senza ricorsione si usa filecmp.cmpfiles(). I parametri sono i nomi delle directory ed un elenco di file da verificare nelle due locazioni. L'elenco dei file comuni dovrebbe contenere solo nomi di file (le directory risultano sempre in una discrepanza) ed i file devono essere presenti nelle due locazioni. Il codice seguente mostra un semplice modo di costruire un elenco comune. Se si pensa di avere una formula più breve, la si invii nei commenti. Il confronto riceve anche il flag shallow, proprio come cmp().

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 'Corrispondenza:', match
print 'Discrepanza:', mismatch
print 'Errori:', errors

cmpfiles() restituisce tre liste di nomi di file per i file che corrispondono, per quelli che non corrispondono e per quelli che non possono essere confrontati (per problemi di permessi o qualsiasi altre ragione).

python filecmp_cmpfiles.py
File comuni: ['non_lo_stesso', 'file_in_dir1', 'file_comune']
Corrispondenza: ['non_lo_stesso', 'file_comune']
Discrepanza: ['file_in_dir1']
Errori: []

Usare dircmp

Le funzioni descritte qui sopra sono indicate per confronti relativamente semplici, ma per un confronto ricorsivo di grandi alberi di directory o per una analisi più completa, la classe dircmp è più utile. Nel suo uso con la casistica più semplice, si può stampare un rapporto confrontando due directory con il metodo report().

import filecmp

filecmp.dircmp('esempio/dir1', 'esempio/dir2').report()

Il risultato è un rapporto con testo semplice che mostra i risultati per il contenuto delle directory fornite, senza ricorsione in questo caso, il file "non_lo_stesso" è ritenuto essere uguale poichè i contenuti non vengono confrontati. Non sembra esserci il modo di far sì che dircmp confronti i contenuti dei file come riesce a fare cmp().

python 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 ottenere maggiori dettagli ed un confronto ricorsivo, usare report_full_closure() :

import filecmp

filecmp.dircmp('esempio/dir1', 'esempio/dir2').report_full_closure()

Il risultato comprende il confronto tra tutte le directory parallele

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

diff esempio/dir1/dir_comune/dir2/dir_solo_in_dir2 esempio/dir2/dir_comune/dir2/dir_solo_in_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_comune esempio/dir2/dir_comune/dir1/dir_comune

diff esempio/dir1/dir_comune/dir1/dir_solo_in_dir1 esempio/dir2/dir_comune/dir1/dir_solo_in_dir1

Usare le differenze in un programma

Oltre a produrre listati stampabili, dircmp calcola delle utili liste di file che si possono usare direttamente in un programma. Ognuno dei seguenti attributi viene calcolato solo quando richiesto, quindi l'istanziare dircmp non produce un sovraccarico supplementare.

I file e le sottodirectory contenute nelle directory che sono oggetto di confronto sono elencate in left_list e right_list:

import filecmp

dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print 'Left :', dc.left_list
print 'Right:', dc.right_list
$ python filecmp_dircmp_list.py
Left : ['dir_comune', 'dir_solo_in_dir1', 'file_comune', 'file_in_dir1', 'file_solo_in_dir1', 'non_lo_stesso']
Right: ['dir_comune', 'dir_solo_in_dir2', 'file_comune', 'file_in_dir1', 'file_solo_in_dir2', 'non_lo_stesso']

Gli input possono essere filtrati passando una lista di nomi da ignorare al costruttore. In modo predefinito i nomi RCS, CVS e tags sono ignorati.

import filecmp

dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2', ignore=['file_comune'])
print 'Left :', dc.left_list
print 'Right:', dc.right_list

In questo caso "file_comune" viene escluso dalla lista di file da confrontare.

$ python filecmp_dircmp_list_filter.py
Left : ['dir_comune', 'dir_solo_in_dir1', 'file_in_dir1', 'file_solo_in_dir1', 'non_lo_stesso']
Right: ['dir_comune', 'dir_solo_in_dir2', 'file_in_dir1', 'file_solo_in_dir2', 'non_lo_stesso']

L'insieme dei file comuni ad entrambe le directory sono mantenuti in common, ed i file univoci in ciascuna directory sono elencati in left_only e right_only .

import filecmp

dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print 'Comuni:', dc.common
print 'Left  :', dc.left_only
print 'Right :', dc.right_only
$ python filecmp_dircmp_membership.py
CComuni: ['file_comune', 'non_lo_stesso', 'dir_comune', 'file_in_dir1']
Left  : ['dir_solo_in_dir1', 'file_solo_in_dir1']
Right : ['file_solo_in_dir2', 'dir_solo_in_dir2']

I membri comuni possono essere successivamente divisi in file, directory ed elementi "funny" (qualsiasi cosa che sia di un tipo diverso nelle due directory o dove si verifichi un errore da os.stat()).

import filecmp

dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print 'Comuni     :', dc.common
print 'Directory  :', dc.common_dirs
print 'File       :', dc.common_files
print 'Funny      :', dc.common_funny

$ python filecmp_dircmp_common.py Nei dati di esempio l'elemento chiamato "file_in_dir1" è un file in una directory ed una sottodirectory in un altra; quindi viene messo nella lista "funny".

Comuni     : ['file_comune', 'non_lo_stesso', 'dir_comune', 'file_in_dir1']
Directory  : ['dir_comune']
File       : ['file_comune', 'non_lo_stesso']
Funny      : ['file_in_dir1']

Le dfferenze tra file sono disposte similarmente

import filecmp

dc = filecmp.dircmp('example/dir1', 'example/dir2')
print 'Same      :', dc.same_files
print 'Different :', dc.diff_files
print 'Funny     :', dc.funny_files

Ricordare che il file "non_lo_stesso" viene confrontato solo tramite os.stat e che il contenuto non viene esaminato.

$ python filecmp_dircmp_diff.py
Uguali    : ['file_comune', 'non_lo_stesso']
Diversi   : []
Funny     : []

Infine, le sottodirectory sono mappate in nuovi oggetti dircmp nell'attributo subdirs per consentire un comodo confronto ricorsivo.

import filecmp

dc = filecmp.dircmp('esempio/dir1', 'esempio/dir2')
print 'Sottodirectory:'
print dc.subdirs
$ python filecmp_dircmp_subdirs.py
Sottodirectory:
{'dir_comune': <filecmp.dircmp instance at 0x93bcb6c>}

Vedere anche:

filecmp
La documentazione della libreria standard per questo modulo.