7.9. filecmp — 文件对比
目的:比较系统上的文件和文件夹.
filecmp
模块包含函数和类来比较系统上的文件和文件夹
示例数据
例子中所讨论的测试文件是由 filecmp_mkexamples.py
创建.
filecmp_mkexamples.py
import os
def mkfile(filename, body=None):
with open(filename, 'w') as f:
f.write(body or filename)
return
def make_example_dir(top):
if not os.path.exists(top):
os.mkdir(top)
curdir = os.getcwd()
os.chdir(top)
os.mkdir('dir1')
os.mkdir('dir2')
mkfile('dir1/file_only_in_dir1')
mkfile('dir2/file_only_in_dir2')
os.mkdir('dir1/dir_only_in_dir1')
os.mkdir('dir2/dir_only_in_dir2')
os.mkdir('dir1/common_dir')
os.mkdir('dir2/common_dir')
mkfile('dir1/common_file', 'this file is the same')
mkfile('dir2/common_file', 'this file is the same')
mkfile('dir1/not_the_same')
mkfile('dir2/not_the_same')
mkfile('dir1/file_in_dir1', 'This is a file in dir1')
os.mkdir('dir2/file_in_dir1')
os.chdir(curdir)
return
if __name__ == '__main__':
os.chdir(os.path.dirname(__file__) or os.getcwd())
make_example_dir('example')
make_example_dir('example/dir1/common_dir')
make_example_dir('example/dir2/common_dir')
运行程序后会在 example
文件夹中创建以下文件:
$ find example | sort
example
example/dir1
example/dir1/common_dir
example/dir1/common_dir/dir1
example/dir1/common_dir/dir1/common_dir
example/dir1/common_dir/dir1/common_file
example/dir1/common_dir/dir1/dir_only_in_dir1
example/dir1/common_dir/dir1/file_in_dir1
example/dir1/common_dir/dir1/file_only_in_dir1
example/dir1/common_dir/dir1/not_the_same
example/dir1/common_dir/dir2
example/dir1/common_dir/dir2/common_dir
example/dir1/common_dir/dir2/common_file
example/dir1/common_dir/dir2/dir_only_in_dir2
example/dir1/common_dir/dir2/file_in_dir1
example/dir1/common_dir/dir2/file_only_in_dir2
example/dir1/common_dir/dir2/not_the_same
example/dir1/common_file
example/dir1/dir_only_in_dir1
example/dir1/file_in_dir1
example/dir1/file_only_in_dir1
example/dir1/not_the_same
example/dir2
example/dir2/common_dir
example/dir2/common_dir/dir1
example/dir2/common_dir/dir1/common_dir
example/dir2/common_dir/dir1/common_file
example/dir2/common_dir/dir1/dir_only_in_dir1
example/dir2/common_dir/dir1/file_in_dir1
example/dir2/common_dir/dir1/file_only_in_dir1
example/dir2/common_dir/dir1/not_the_same
example/dir2/common_dir/dir2
example/dir2/common_dir/dir2/common_dir
example/dir2/common_dir/dir2/common_file
example/dir2/common_dir/dir2/dir_only_in_dir2
example/dir2/common_dir/dir2/file_in_dir1
example/dir2/common_dir/dir2/file_only_in_dir2
example/dir2/common_dir/dir2/not_the_same
example/dir2/common_file
example/dir2/dir_only_in_dir2
example/dir2/file_in_dir1
example/dir2/file_only_in_dir2
example/dir2/not_the_same
在 common_dir
目录下重复一次相同的文件结构来提供递归选项
比较文件
cmp()
方法用于比较系统上两个文件。
filecmp_cmp.py
import filecmp
print('common_file :', end=' ')
print(filecmp.cmp('example/dir1/common_file',
'example/dir2/common_file'),
end=' ')
print(filecmp.cmp('example/dir1/common_file',
'example/dir2/common_file',
shallow=False))
print('not_the_same:', end=' ')
print(filecmp.cmp('example/dir1/not_the_same',
'example/dir2/not_the_same'),
end=' ')
print(filecmp.cmp('example/dir1/not_the_same',
'example/dir2/not_the_same',
shallow=False))
print('identical :', end=' ')
print(filecmp.cmp('example/dir1/file_only_in_dir1',
'example/dir1/file_only_in_dir1'),
end=' ')
print(filecmp.cmp('example/dir1/file_only_in_dir1',
'example/dir1/file_only_in_dir1',
shallow=False))
shallow
参数告诉 cmp()
除了对比文件元数据是否还要比较文件内容。默认只是比较从 os.stat()
获取到的文件元信息,如果 stat
相同那么文件就相同,因此同时创建并且大小相同的文件被认为是相同的,即使它们的内容不同。当 shallow
是 False
的时候,文件内容也会参与比较。
$ python3 filecmp_cmp.py
common_file : True True
not_the_same: True False
identical : True True
要在不递归的情况下比较两个目录中的一组文件,请使用 cmpfiles()
。这个方法参数是两个目录名称以及要比较的公共文件列表。公共文件列表应该只包含文件名(如果包含目录会导致不匹配)而且文件必须在两个目录都存在。下面的例子展示了一个构建公共文件列表的简单方法。cmpfiles()
也可以传入一个 shallow
参数,意义同 cmp()
。
filecmp_cmpfiles.py
import filecmp
import os
# 构建公共文件列表
d1_contents = set(os.listdir('example/dir1'))
d2_contents = set(os.listdir('example/dir2'))
common = list(d1_contents & d2_contents)
common_files = [
f
for f in common
if os.path.isfile(os.path.join('example/dir1', f))
]
print('Common files:', common_files)
# 比较目录
match, mismatch, errors = filecmp.cmpfiles(
'example/dir1',
'example/dir2',
common_files,
)
print('Match :', match)
print('Mismatch :', mismatch)
print('Errors :', errors)
cmpfiles()
方法返回三个文件名列表,依次为匹配的文件列表,不匹配的文件列表以及不能比较的(权限问题或者其他任何问题)。
$ python3 filecmp_cmpfiles.py
Common files: ['not_the_same', 'file_in_dir1', 'common_file']
Match : ['not_the_same', 'common_file']
Mismatch : ['file_in_dir1']
Errors : []
目录比较
前面介绍的方法都只是适用于相对简单的比较。对于递归比较大目录树或者需要完整的分析的时候,dircmp()
方法通常比较有用。简单使用的时候,可以用 report()
方法打印比较报告。
filecmp_dircmp_report.py
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
dc.report()
输出是文本形式的,仅仅展示了给定的两个目录比较结果而没有递归。
这个例子中 「not_the_same
」被认为是相同的是因为没有比较内容。这里没有办法像 cmp()
那样比较文件内容。
$ python3 filecmp_dircmp_report.py
diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file', 'not_the_same']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']
要看更多详细的报告,使用 report_full_closure()
:
filecmp_dircmp_report_full_closure.py
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
dc.report_full_closure()
输出包括所有子目录的比较。
$ python3 filecmp_dircmp_report_full_closure.py
diff example/dir1 example/dir2
Only in example/dir1 : ['dir_only_in_dir1', 'file_only_in_dir1']
Only in example/dir2 : ['dir_only_in_dir2', 'file_only_in_dir2']
Identical files : ['common_file', 'not_the_same']
Common subdirectories : ['common_dir']
Common funny cases : ['file_in_dir1']
diff example/dir1/common_dir example/dir2/common_dir
Common subdirectories : ['dir1', 'dir2']
diff example/dir1/common_dir/dir1 example/dir2/common_dir/dir1
Identical files : ['common_file', 'file_in_dir1',
'file_only_in_dir1', 'not_the_same']
Common subdirectories : ['common_dir', 'dir_only_in_dir1']
diff example/dir1/common_dir/dir1/dir_only_in_dir1
example/dir2/common_dir/dir1/dir_only_in_dir1
diff example/dir1/common_dir/dir1/common_dir
example/dir2/common_dir/dir1/common_dir
diff example/dir1/common_dir/dir2 example/dir2/common_dir/dir2
Identical files : ['common_file', 'file_only_in_dir2',
'not_the_same']
Common subdirectories : ['common_dir', 'dir_only_in_dir2',
'file_in_dir1']
diff example/dir1/common_dir/dir2/common_dir
example/dir2/common_dir/dir2/common_dir
diff example/dir1/common_dir/dir2/file_in_dir1
example/dir2/common_dir/dir2/file_in_dir1
diff example/dir1/common_dir/dir2/dir_only_in_dir2
example/dir2/common_dir/dir2/dir_only_in_dir2
程序内使用差异性
除了生成报告之外,dircmp()
还可以在程序中比较两个目录并且使用比较结果。下列的每个属性只有在使用时会被计算,所以创建一个 dircmp
的实例不会增加未使用数据的开销。
filecmp_dircmp_list.py
import filecmp
import pprint
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print('Left:')
pprint.pprint(dc.left_list)
print('\nRight:')
pprint.pprint(dc.right_list)
目录中包含的被比较的文件和子目录可以使用 left_list
和 right_list
列出。
$ python3 filecmp_dircmp_list.py
Left:
['common_dir',
'common_file',
'dir_only_in_dir1',
'file_in_dir1',
'file_only_in_dir1',
'not_the_same']
Right:
['common_dir',
'common_file',
'dir_only_in_dir2',
'file_in_dir1',
'file_only_in_dir2',
'not_the_same']
可以传入一个要忽略的文件名列表给构造器,默认是 RCS
,CVS
和 tags
。
filecmp_dircmp_list_filter.py
import filecmp
import pprint
dc = filecmp.dircmp('example/dir1', 'example/dir2',
ignore=['common_file'])
print('Left:')
pprint.pprint(dc.left_list)
print('\nRight:')
pprint.pprint(dc.right_list)
这个例子中,「common_file
」不在要比较的文件列表中。
$ python3 filecmp_dircmp_list_filter.py
Left:
['common_dir',
'dir_only_in_dir1',
'file_in_dir1',
'file_only_in_dir1',
'not_the_same']
Right:
['common_dir',
'dir_only_in_dir2',
'file_in_dir1',
'file_only_in_dir2',
'not_the_same']
两个目录的公共文件保存在 common
属性中,每个目录的唯一文件可以由 left_only
和 right_only
获取。
filecmp_dircmp_membership.py
import filecmp
import pprint
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print('Common:')
pprint.pprint(dc.common)
print('\nLeft:')
pprint.pprint(dc.left_only)
print('\nRight:')
pprint.pprint(dc.right_only)
「left
」目录是传入 dircmp()
方法的第一个参数,「right
」目录是第二个参数。
$ python3 filecmp_dircmp_membership.py
Common:
['file_in_dir1', 'common_file', 'common_dir', 'not_the_same']
Left:
['dir_only_in_dir1', 'file_only_in_dir1']
Right:
['file_only_in_dir2', 'dir_only_in_dir2']
公共成员可以进一步分解为公共文件,公共目录以及 「funny
」项目(存在于两个目录但有不同类型或者 os.stat()
返回出错)。
filecmp_dircmp_common.py
import filecmp
import pprint
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print('Common:')
pprint.pprint(dc.common)
print('\nDirectories:')
pprint.pprint(dc.common_dirs)
print('\nFiles:')
pprint.pprint(dc.common_files)
print('\nFunny:')
pprint.pprint(dc.common_funny)
这个例子中,file_in_dir1
在一个目录中是一个文件,而在另一个中是个子目录,所以它才出现在 funny
列表中。
$ python3 filecmp_dircmp_common.py
Common:
['file_in_dir1', 'common_file', 'common_dir', 'not_the_same']
Directories:
['common_dir']
Files:
['common_file', 'not_the_same']
Funny:
['file_in_dir1']
文件之间的差异同样被分解。
filecmp_dircmp_diff.py
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print('Same :', dc.same_files)
print('Different :', dc.diff_files)
print('Funny :', dc.funny_files)
not_the_same
仅仅比较了 os.stat()
返回的元信息并没有检查内容,
所以它出现在了 same_files
列表中。
$ python3 filecmp_dircmp_diff.py
Same : ['common_file', 'not_the_same']
Different : []
Funny : []
最后还保存了子目录以允许简单的递归比较。
filecmp_dircmp_subdirs.py
import filecmp
dc = filecmp.dircmp('example/dir1', 'example/dir2')
print('Subdirectories:')
print(dc.subdirs)
subdirs
属性是将目录名称映射到一个新的 dircmp
对象。
$ python3 filecmp_dircmp_subdirs.py
Subdirectories:
{'common_dir': <filecmp.dircmp object at 0x1019b2be0>}
推荐阅读
- filecmp 标准库文档
difflib
-- 比较两个序列之间的差异性。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。