web-dev-qa-db-fra.com

dircmp récursif (comparez deux répertoires pour vous assurer qu'ils ont les mêmes fichiers et sous-répertoires)

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.

23
Gregg Lind

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
20
Mateusz Kobos

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).

14
Philippe Ombredanne

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é.

5
asthasr

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.

2
Katriel

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'))
1
Guillaume Vincent

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')
1
Raullen Chai

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)
0
alzix
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
0
NotAUser

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)
0
Rok

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)
0
Gregg Lind