D'après ce que j'observe filecmp.dircmp
est récursif, mais inadéquat pour mes besoins , au moins en py2. Je veux comparer deux répertoires et tous leurs fichiers contenus. Est-ce que cela existe ou dois-je construire (en utilisant os.walk
, par exemple). Je préfère le pré-construit, où quelqu'un d'autre a déjà fait le test unitaire :)
La «comparaison» peut être bâclée (ignorer les autorisations, par exemple), si cela vous aide.
Je voudrais quelque chose de booléen, et report_full_closure
est un rapport imprimé. Il ne descend également que les sous-répertoires communs. AFIAC, s’ils ont quelque chose à gauche ou à droite du répertoire seulement ceux-ci sont des répertoires différents. Je construis cela en utilisant os.walk
à la place.
Voici une implémentation alternative de la fonction de comparaison avec le module filecmp
. Il utilise une récursion au lieu de os.walk
, donc c'est un peu plus simple. Cependant, cela ne se produit pas simplement en utilisant les attributs common_dirs
et subdirs
car dans ce cas, nous utiliserions implicitement la mise en œuvre par défaut "superficielle" de la comparaison de fichiers, ce qui n'est probablement pas ce que vous souhaitez. Dans l'implémentation ci-dessous, lors de la comparaison de fichiers portant le même nom, nous ne comparons toujours que leur contenu.
import filecmp
import os.path
def are_dir_trees_equal(dir1, dir2):
"""
Compare two directories recursively. Files in each directory are
assumed to be equal if their names and contents are equal.
@param dir1: First directory path
@param dir2: Second directory path
@return: True if the directory trees are the same and
there were no errors while accessing the directories or files,
False otherwise.
"""
dirs_cmp = filecmp.dircmp(dir1, dir2)
if len(dirs_cmp.left_only)>0 or len(dirs_cmp.right_only)>0 or \
len(dirs_cmp.funny_files)>0:
return False
(_, mismatch, errors) = filecmp.cmpfiles(
dir1, dir2, dirs_cmp.common_files, shallow=False)
if len(mismatch)>0 or len(errors)>0:
return False
for common_dir in dirs_cmp.common_dirs:
new_dir1 = os.path.join(dir1, common_dir)
new_dir2 = os.path.join(dir2, common_dir)
if not are_dir_trees_equal(new_dir1, new_dir2):
return False
return True
filecmp.dircmp
est la voie à suivre. Mais il ne compare pas le contenu des fichiers trouvés avec le même chemin dans deux répertoires comparés. Au lieu de cela, filecmp.dircmp
ne regarde que les attributs de fichiers. Puisque dircmp
est une classe, vous corrigez le problème avec une sous-classe dircmp
et remplacez sa fonction phase3
qui compare les fichiers pour garantir la comparaison du contenu au lieu de ne comparer que les attributs os.stat
.
import filecmp
class dircmp(filecmp.dircmp):
"""
Compare the content of dir1 and dir2. In contrast with filecmp.dircmp, this
subclass compares the content of files with the same path.
"""
def phase3(self):
"""
Find out differences between common files.
Ensure we are using content comparison with shallow=False.
"""
fcomp = filecmp.cmpfiles(self.left, self.right, self.common_files,
shallow=False)
self.same_files, self.diff_files, self.funny_files = fcomp
Ensuite, vous pouvez utiliser ceci pour retourner un booléen:
import os.path
def is_same(dir1, dir2):
"""
Compare two directory trees content.
Return False if they differ, True is they are the same.
"""
compared = dircmp(dir1, dir2)
if (compared.left_only or compared.right_only or compared.diff_files
or compared.funny_files):
return False
for subdir in compared.common_dirs:
if not is_same(os.path.join(dir1, subdir), os.path.join(dir2, subdir)):
return False
return True
Si vous souhaitez réutiliser cet extrait de code, il est dédié au domaine public ou au Creative Commons CC0 de votre choix (en plus de la licence par défaut CC-BY-SA fournie par SO).
La méthode report_full_closure()
est récursive:
comparison = filecmp.dircmp('/directory1', '/directory2')
comparison.report_full_closure()
Edition: Après l'édition du PO, je dirais qu'il vaut mieux utiliser les autres fonctions de filecmp
. Je pense que os.walk
est inutile; Il est préférable de simplement récapituler dans les listes générées par common_dirs
, etc., bien que dans certains cas (grandes arborescences de répertoires), cela risque de provoquer une erreur de profondeur de récursion maximale s'il est mal implémenté.
dircmp
peut être récursif: voir report_full_closure
.
Autant que je sache, dircmp
n'offre pas de fonction de comparaison d'annuaire. Il serait cependant très facile d’écrire le vôtre; utilisez left_only
et right_only
sur dircmp
pour vérifier que les fichiers dans les répertoires sont les mêmes, puis effectuez une récurrence sur l'attribut subdirs
.
Voici une solution simple avec une fonction récursive:
import filecmp
def same_folders(dcmp):
if dcmp.diff_files:
return False
for sub_dcmp in dcmp.subdirs.values():
return same_folders(sub_dcmp)
return True
same_folders(filecmp.dircmp('/tmp/archive1', '/tmp/archive2'))
Une autre solution pour comparer la disposition de dir1 et dir2, ignorer le contenu des fichiers
Voir Gist ici: https://Gist.github.com/4164344
Edit : voici le code, au cas où le Gist serait perdu pour une raison quelconque:
import os
def compare_dir_layout(dir1, dir2):
def _compare_dir_layout(dir1, dir2):
for (dirpath, dirnames, filenames) in os.walk(dir1):
for filename in filenames:
relative_path = dirpath.replace(dir1, "")
if os.path.exists( dir2 + relative_path + '\\' + filename) == False:
print relative_path, filename
return
print 'files in "' + dir1 + '" but not in "' + dir2 +'"'
_compare_dir_layout(dir1, dir2)
print 'files in "' + dir2 + '" but not in "' + dir1 +'"'
_compare_dir_layout(dir2, dir1)
compare_dir_layout('xxx', 'yyy')
Basé sur le problème python 12932 et la documentation de filecmp vous pouvez utiliser l'exemple suivant:
import os
import filecmp
# force content compare instead of os.stat attributes only comparison
filecmp.cmpfiles.__defaults__ = (False,)
def _is_same_helper(dircmp):
assert not dircmp.funny_files
if dircmp.left_only or dircmp.right_only or dircmp.diff_files or dircmp.funny_files:
return False
for sub_dircmp in dircmp.subdirs.values():
if not _is_same_helper(sub_dircmp):
return False
return True
def is_same(dir1, dir2):
"""
Recursively compare two directories
:param dir1: path to first directory
:param dir2: path to second directory
:return: True in case directories are the same, False otherwise
"""
if not os.path.isdir(dir1) or not os.path.isdir(dir2):
return False
dircmp = filecmp.dircmp(dir1, dir2)
return _is_same_helper(dircmp)
def same(dir1, dir2):
"""Returns True if recursively identical, False otherwise
"""
c = filecmp.dircmp(dir1, dir2)
if c.left_only or c.right_only or c.diff_files or c.funny_files:
return False
else:
safe_so_far = True
for i in c.common_dirs:
same_so_far = same_so_far and same(os.path.join(frompath, i), os.path.join(topath, i))
if not same_so_far:
break
return same_so_far
Cela vérifiera si les fichiers sont aux mêmes emplacements et si leur contenu est identique. Il ne sera pas correctement validé pour les sous-dossiers vides.
import filecmp
import glob
import os
path_1 = '.'
path_2 = '.'
def folders_equal(f1, f2):
file_pairs = list(Zip(
[x for x in glob.iglob(os.path.join(f1, '**'), recursive=True) if os.path.isfile(x)],
[x for x in glob.iglob(os.path.join(f2, '**'), recursive=True) if os.path.isfile(x)]
))
locations_equal = any([os.path.relpath(x, f1) == os.path.relpath(y, f2) for x, y in file_pairs])
files_equal = all([filecmp.cmp(*x) for x in file_pairs])
return locations_equal and files_equal
folders_equal(path_1, path_2)
Voici ma solution: Gist
def dirs_same_enough(dir1,dir2,report=False):
''' use os.walk and filecmp.cmpfiles to
determine if two dirs are 'same enough'.
Args:
dir1, dir2: two directory paths
report: if True, print the filecmp.dircmp(dir1,dir2).report_full_closure()
before returning
Returns:
bool
'''
# os walk: root, list(dirs), list(files)
# those lists won't have consistent ordering,
# os.walk also has no guaranteed ordering, so have to sort.
walk1 = sorted(list(os.walk(dir1)))
walk2 = sorted(list(os.walk(dir2)))
def report_and_exit(report,bool_):
if report:
filecmp.dircmp(dir1,dir2).report_full_closure()
return bool_
else:
return bool_
if len(walk1) != len(walk2):
return false_or_report(report)
for (p1,d1,fl1),(p2,d2,fl2) in Zip(walk1,walk2):
d1,fl1, d2, fl2 = set(d1),set(fl1),set(d2),set(fl2)
if d1 != d2 or fl1 != fl2:
return report_and_exit(report,False)
for f in fl1:
same,diff,weird = filecmp.cmpfiles(p1,p2,fl1,shallow=False)
if diff or weird:
return report_and_exit(report,False)
return report_and_exit(report,True)