Рекурсивно сравнивайте два каталога, чтобы убедиться, что они имеют одинаковые файлы и подкаталоги
Из того, что я наблюдаю filecmp.dircmp
, является рекурсивным, но неадекватным для моих нужд, по крайней мере, в py2. Я хочу сравнить два каталога и все их содержащиеся файлы. Существует ли это, или мне нужно построить (например, os.walk
). Я предпочитаю предварительно построенный, где кто-то еще уже выполнил модульное тестирование:)
Фактическое "сравнение" может быть неаккуратным (например, игнорировать разрешения), если это помогает.
Мне нужно что-то логическое, а report_full_closure
- печатный отчет. Он также распространяется только на общие субдиры. AFIAC, если у них есть что-либо в левом или правом каталоге, это только разные. Я создаю это вместо os.walk
.
Ответы
Ответ 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)