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

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

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

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

Ответы [ 10 ]

21 голосов
/ 13 июля 2011

Вот альтернативная реализация функции сравнения с модулем 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
14 голосов
/ 21 июля 2014

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

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

5 голосов
/ 15 ноября 2010

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

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

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

3 голосов
/ 13 июня 2016

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

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'))
2 голосов
/ 29 ноября 2012

Еще одно решение для сравнения раскладок 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')
2 голосов
/ 15 ноября 2010

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

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

0 голосов
/ 19 января 2019

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

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 голосов
/ 02 декабря 2017

На основе 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)
0 голосов
/ 25 сентября 2012
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 голосов
/ 16 ноября 2010

Вот мое решение: Суть

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...