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 "funny".

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

Vedere anche:

filecmp
La documentazione della libreria standard per questo modulo
difflib
Calcolare le differenze tra due sequenze.