Подтвердить что ты не робот

Рекурсивно сравнивайте два каталога, чтобы убедиться, что они имеют одинаковые файлы и подкаталоги

Из того, что я наблюдаю filecmp.dircmp, является рекурсивным, но неадекватным для моих нужд, по крайней мере, в py2. Я хочу сравнить два каталога и все их содержащиеся файлы. Существует ли это, или мне нужно построить (например, os.walk). Я предпочитаю предварительно построенный, где кто-то еще уже выполнил модульное тестирование:)

Фактическое "сравнение" может быть неаккуратным (например, игнорировать разрешения), если это помогает.

Мне нужно что-то логическое, а report_full_closure - печатный отчет. Он также распространяется только на общие субдиры. AFIAC, если у них есть что-либо в левом или правом каталоге, это только разные. Я создаю это вместо os.walk.

4b9b3361

Ответ 1

Здесь представлена ​​альтернативная реализация функции сравнения с модулем filecmp. Он использует рекурсию вместо os.walk, поэтому она немного проще. Однако он не перезаписывается просто с помощью атрибутов common_dirs и subdirs, так как в этом случае мы будем неявно использовать стандартную "мелкую" реализацию сравнения файлов, которая, вероятно, не является тем, что вы хотите. В приведенной ниже реализации при сравнении файлов с тем же именем мы всегда сравниваем только их содержимое.

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

Ответ 2

filecmp.dircmp - путь. Но он не сравнивает содержимое файлов, найденных с одним и тем же путем, в двух сравниваемых каталогах. Вместо этого filecmp.dircmp просматривает только атрибуты файлов. Поскольку dircmp является классом, вы исправляете это с подклассом dircmp и переопределяете его функцию phase3, которая сравнивает файлы, чтобы обеспечить сравнение содержимого, а не только сравнение атрибутов 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

Затем вы можете использовать это, чтобы вернуть логическое значение:

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

В случае, если вы хотите повторно использовать этот фрагмент кода, он настоящим предназначен для общедоступного домена или Creative Commons CC0 по вашему выбору (в дополнение к лицензии CC-BY-SA по умолчанию, предоставленной SO).

Ответ 3

Метод report_full_closure() рекурсивный:

comparison = filecmp.dircmp('/directory1', '/directory2')
comparison.report_full_closure()

Изменить: после редактирования OP я бы сказал, что лучше всего использовать другие функции в filecmp. Я думаю, что os.walk не нужно; лучше просто перечислить списки, созданные с помощью common_dirs и т.д., хотя в некоторых случаях (большие деревья каталогов) это может привести к ошибке ошибки Max Recursion Depth, если она выполнена плохо.

Ответ 4

Здесь простое решение с рекурсивной функцией:

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

Ответ 5

dircmp может быть рекурсивным: см. report_full_closure.

Насколько я знаю, dircmp не предлагает функцию сравнения каталогов. Однако было бы очень легко написать свой собственный; используйте left_only и right_only на dircmp, чтобы проверить, что файлы в каталогах одинаковы, а затем перезаписывать атрибут subdirs.

Ответ 6

Другое решение для сравнения выкладки из dir1 и dir2, игнорирует содержимое файлов

Смотрите здесь: https://gist.github.com/4164344

Изменить: здесь код, в случае, если суть почему-то потерялась:

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

Ответ 7

Вот мое решение: 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)

Ответ 8

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

Ответ 9

На основе python issue 12932 и документация filecmp вы можете использовать следующий пример:

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)

Ответ 10

Это проверит, находятся ли файлы в тех же местах и имеют ли они одинаковое содержимое. Он не будет правильно проверяться для пустых подпапок.

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)