递归比较两个目录以确保文件和子目录相同
根据我的观察,filecmp.dircmp
是一种可以递归比较的工具,但对我来说不太够用,至少在Python 2中是这样。我想比较两个文件夹及其里面所有的文件。请问有没有现成的工具可以用,还是说我需要自己动手写一个(比如用os.walk
)?我更喜欢那些已经有人做过单元测试的现成工具,这样省事多了 :)
其实“比较”的过程可以简单点(比如可以忽略文件权限),如果这样能帮到你。
我想要的结果是一个布尔值,而report_full_closure
是一个打印出来的报告。它只会比较共同的子目录。对我来说,如果左边或右边的文件夹里有东西,只有那些就是不同的文件夹。我是用os.walk
来实现这个功能的。
15 个回答
这里有一个简单的解决方案,使用了递归函数:
import filecmp
def same_folders(dcmp):
if dcmp.diff_files or dcmp.left_only or dcmp.right_only:
return False
for sub_dcmp in dcmp.subdirs.values():
if not same_folders(sub_dcmp):
return False
return True
same_folders(filecmp.dircmp('/tmp/archive1', '/tmp/archive2'))
filecmp.dircmp
是一个很好的工具,但它只比较两个目录中相同路径的文件的属性,而不比较文件的内容。也就是说,filecmp.dircmp
只关注文件的基本信息,比如大小、修改时间等。因为 dircmp
是一个类,所以你可以通过创建一个 dircmp
的子类,并重写它的 phase3
函数,来实现文件内容的比较,而不仅仅是比较文件的属性。
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
如果你想重复使用这个代码片段,它可以被视为公共领域的作品,或者根据你的选择使用创意共享 CC0 许可(除了 SO 默认提供的 CC-BY-SA 许可)。
这里有一个使用 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