[toc]
文件/目录差异对比法
当我们进行代码审计或校验备份时,往往需要检查原始与目标目录的一致性;
Python的标准库已经自带了满足此需求的模块filecmp;
filecmp可以实现文件、目录遍历子目录的差异对比功能;
比如报告中输出目录目录比原始多出的文件或子目录;
即使文件同名也会判断是否为同一个文件(内容级比对)等;
python2.3 或更高版本默认自带fileemp模块,无需额外安装;
模块常用方法说明
filecmp 提供了三个操作方法,分别为cmp(单文件对比)、cmpfiles(多文件对比)
dircmp(目录对比)
单文件对比,采用filecmp.cmp(f1,f2[,shallow])方法,比较文件名为f1和f2的文件
相同反回True,不相同返回False,shallow默认为True,意思是只根据os.stat()方法
返回的文件基本信息进行对比,比如最后访问时间、修改时间、状态改变时间等
忽略对文件内容的对比。当shallow为False时,则os.stat()与文件内容同时进行校验
# 对比文件的差异
➜ test echo "good user" > f1
➜ test echo "good user" > f3
➜ test echo "good user2" > f2
In [2]: import filecmp
In [3]: filecmp.cmp("/Users/macbookdzsbe/Desktop/MyPython/test/f1","/Users/macbookdzsbe/Desktop/MyPython/test/f3")
Out[3]: True
In [4]: filecmp.cmp("/Users/macbookdzsbe/Desktop/MyPython/test/f1","/Users/macbookdzsbe/Desktop/MyPython/test/f2")
Out[4]: False
dir1 与 dir2目录中指定文件清单对比
两目录下文件的 md5 信息如下
两目录下文件的 md5 信息如下,其中f1、f2匹配; f3不匹配
f4,f5对应的目录中不存在无法比较
[root@test dir2]# md5sum ../dir1/*
1a9dbd408f626389539e9feb1d234df7 ../dir1/f1
235dce31eebce0213322102f698bc249 ../dir1/f2
1a9dbd408f626389539e9feb1d234df7 ../dir1/f3
d41d8cd98f00b204e9800998ecf8427e ../dir1/f5
[root@test dir2]# md5sum *
1a9dbd408f626389539e9feb1d234df7 f1
235dce31eebce0213322102f698bc249 f2
68e51baf228d447b199a75268dd5634b f3
764c976782dec989b043d08714ec21a5 f4
# 使用cmpfiles 对比的结果如下,符合我们的预期
In [4]: import filecmp
In [5]: filecmp.cmpfiles("/root/test/dir1", "/root/test/dir2",['f1','f2','f3','f4','f5'])
Out[5]: (['f1', 'f2'], ['f3'], ['f4', 'f5'])
目录对比,能过dircmp(a,b[,ingore[,hide]]) 类创建一个目录比较对象;
其中a和b是参加比较的目录名。ignore代表文件名忽略的列表,并默认为[‘RCS’,’CVS’,’’tags];
hide代表隐藏的列表,默认为[os.curdir, os.pardir]。dircmp类可以获得目录比较的详细信息;
如只有在a目录中包括的文件、a与b都存在的子目录、匹配的文件等,同时支持递归;
dircmp提供了三个输出报的方法
# report(),比较当前指定目录中的内容;
# report_partial_closure(),比较当前指定目录 及第一级子目录中的内容;
# report_full_closure(),递归比较所有指定目录的内容;
# 为输出更加详细的比较内容,dircmp类还提供了以下属性;
# left, 左目录,如类定义的a;
# right, 右目录,如类定义中的b;
# left_list, 左目录中的文件及目录列表;
# right_list, 右目录中的文件及目录列表;
# common, 两边目录共同存在的文件或者目录;
# left_only, 只在左目录中的文件或者目录;
# right_only, 只在右目录的文件或者目录;
# common_dirs, 两边目录都存在的子目录;
# common_files, 两边目录都存在的子文件;
# common_funny, 两边目录都存在的子目录(不同目录类型或os.stat()记录的错误);
# same_files, 匹配相同的文件;
# diff_files, 不匹配的文件;
# funny_files, 两边目录中都存在,但无法比较的文件;
# subdirs, 将common_dirs目录名映身到新的dircmp对象,格式为字典类型;
对比dir1与dir2目录差异
[root@test test]# mkdir dir1/{a/{a1,b/{b1,b2,b3}},f1,f2,f3,f4} -pv
mkdir: 已创建目录 "dir1"
mkdir: 已创建目录 "dir1/a"
mkdir: 已创建目录 "dir1/a/a1"
mkdir: 已创建目录 "dir1/a/b"
mkdir: 已创建目录 "dir1/a/b/b1"
mkdir: 已创建目录 "dir1/a/b/b2"
mkdir: 已创建目录 "dir1/a/b/b3"
mkdir: 已创建目录 "dir1/f1"
mkdir: 已创建目录 "dir1/f2"
mkdir: 已创建目录 "dir1/f3"
mkdir: 已创建目录 "dir1/f4"
[root@test test]# touch dir1/test.py
[root@test test]# tree dir1/
dir1/
├── a
│ ├── a1
│ └── b
│ ├── b1
│ ├── b2
│ └── b3
├── f1
├── f2
├── f3
├── f4
└── test.py
[root@tf-test test]# mkdir dir2/{a/{a1,b/{b1,b2,b3}},aa{/aa1},f1,f2,f3,f5} -pv
mkdir: 已创建目录 "dir2"
mkdir: 已创建目录 "dir2/a"
mkdir: 已创建目录 "dir2/a/a1"
mkdir: 已创建目录 "dir2/a/b"
mkdir: 已创建目录 "dir2/a/b/b1"
mkdir: 已创建目录 "dir2/a/b/b2"
mkdir: 已创建目录 "dir2/a/b/b3"
mkdir: 已创建目录 "dir2/aa{"
mkdir: 已创建目录 "dir2/aa{/aa1}"
mkdir: 已创建目录 "dir2/f1"
mkdir: 已创建目录 "dir2/f2"
mkdir: 已创建目录 "dir2/f3"
mkdir: 已创建目录 "dir2/f5"
[root@tf-test test]# touch dir2/test.py
[root@tf-test test]# tree dir2/
dir2/
├── a
│ ├── a1
│ └── b
│ ├── b1
│ ├── b2
│ └── b3
├── aa{
│ └── aa1}
├── f1
├── f2
├── f3
├── f5
└── test.py
[root@test test]# cat cmpy.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import filecmp
a = "/root/test/dir1" # 定义左目录
b = "/root/test/dir2" # 定义右目录
dirobj=filecmp.dircmp(a,b,['test.y']) # 目录比较,忽略test.py文件
#输出对比结果数据报表
dirobj.report()
dirobj.report_partial_closure()
dirobj.report_full_closure()
print "left_list:" + str(dirobj.left_list)
print "right_list" + str(dirobj.right_list)
print "common:" + str(dirobj.common)
print "left_only:" + str(dirobj.left_only)
print "right_only:" + str(dirobj.right_only)
print "common_dirs:" + str(dirobj.common_dirs)
print "common_files:" + str(dirobj.common_files)
print "common_funny:" + str(dirobj.common_funny)
print "same_file:" + str(dirobj.same_files)
print "diff_files:" + str(dirobj.diff_files)
print "funny_files:" + str(dirobj.funny_files)
diff /root/test/dir1 /root/test/dir2
Only in /root/test/dir1 : ['f4']
Only in /root/test/dir2 : ['aa{', 'f5']
Identical files : ['test.py']
Common subdirectories : ['a', 'f1', 'f2', 'f3']
diff /root/test/dir1 /root/test/dir2
Only in /root/test/dir1 : ['f4']
Only in /root/test/dir2 : ['aa{', 'f5']
Identical files : ['test.py']
Common subdirectories : ['a', 'f1', 'f2', 'f3']
diff /root/test/dir1/a /root/test/dir2/a
Common subdirectories : ['a1', 'b']
diff /root/test/dir1/f1 /root/test/dir2/f1
diff /root/test/dir1/f2 /root/test/dir2/f2
diff /root/test/dir1/f3 /root/test/dir2/f3
diff /root/test/dir1 /root/test/dir2
Only in /root/test/dir1 : ['f4']
Only in /root/test/dir2 : ['aa{', 'f5']
Identical files : ['test.py']
Common subdirectories : ['a', 'f1', 'f2', 'f3']
diff /root/test/dir1/a /root/test/dir2/a
Common subdirectories : ['a1', 'b']
diff /root/test/dir1/a/a1 /root/test/dir2/a/a1
diff /root/test/dir1/a/b /root/test/dir2/a/b
Common subdirectories : ['b1', 'b2', 'b3']
diff /root/test/dir1/a/b/b1 /root/test/dir2/a/b/b1
diff /root/test/dir1/a/b/b2 /root/test/dir2/a/b/b2
diff /root/test/dir1/a/b/b3 /root/test/dir2/a/b/b3
diff /root/test/dir1/f1 /root/test/dir2/f1
diff /root/test/dir1/f2 /root/test/dir2/f2
diff /root/test/dir1/f3 /root/test/dir2/f3
left_list:['a', 'f1', 'f2', 'f3', 'f4', 'test.py']
right_list['a', 'aa{', 'f1', 'f2', 'f3', 'f5', 'test.py']
common:['a', 'f1', 'f2', 'f3', 'test.py']
left_only:['f4']
right_only:['aa{', 'f5']
common_dirs:['a', 'f1', 'f2', 'f3']
common_files:['test.py']
common_funny:[]
same_file:['test.py']
diff_files:[]
funny_files:[]
校验源与备份目录差异
有时候我们无法确认备份目录与源目录文件是否保持一致,包括源目录中的新文件或目录;
更新文件或目录 有无成功同步,定期进行校验,没有成功则希望有针对性地进行补备份;
本未例使用了filecmp模块的left_only、diff_files方法递归获取目录的更新项;
再通过shuti.copyfile、 os.makedirs 方法对更新项进行复制,最终保持一致状态;
➜ test cat dir_contrast.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys
import filecmp
import re
import shutil
import difflib
holderlist = []
def compareme(dir1,dir2): # 递归获取更新项函数
dircomp=filecmp.dircmp(dir1,dir2)
only_in_one=dircomp.left_only # 源目录新文件或目录
diff_in_one=dircomp.diff_files # 不匹配文件,源目录文件已发生变化
dirath=os.path.abspath(dir1) # 定义源目录绝对路径
#将更新文件名或目录追回到holderlist
[holderlist.append(os.path.abspath(os.path.join(dir1,x))) for x in only_in_one]
[holderlist.append(os.path.abspath(os.path.join(dir1,x))) for x in diff_in_one]
if len (dircomp.common_dirs) > 0: # 判断是否存在相同子目录,以便递归
for item in dircomp.common_dirs: #递归子目录
compareme(os.path.abspath(os.path.join(dir1, item)), \
os.path.abspath(os.path.join(dir2,item)))
return holderlist
def main():
if len(sys.argv) >2: # 要求输入源目录与备份目录
dir1 = sys.argv[1]
dir2 = sys.argv[2]
else:
print "Usage:", sys.argv[0], "datadir backupdir"
sys.exit()
source_files = compareme(dir1,dir2)
dir1 = os.path.abspath(dir1)
if not dir2.endswith('/'): dir2=dir2+'/' #备份目录路径加"/"符
dir2=os.path.abspath(dir2)
destination_files=[]
createdir_bool = False
for item in source_files: # 遍历返回的差异文件或目录清单
destination_dir= re.sub(dir1,dir2, item) # 将源目录差异路径清单对应替换成备份目录
destination_files.append(destination_dir)
if os.path.isdir(item): # 如果差异路径 为目录且不存在,则在备份目录中创建
if not os.path.exists(destination_dir):
os.makedirs(destination_dir)
createdir_bool = True #再次调用compareme 函数标记
if createdir_bool: #重新调用compareme函数,重新遍历新创建目录的内容
destination_files = []
source_files=[]
source_files=compareme(dir1,dir2) # 调用compareme函数
for item in source_files: #获取目录差异路径清单,对应替换成备分目录
destination_dir = re.sub(dir1,dir2, item)
destination_files.append(destination_dir)
print "update item:"
print source_files #输出更新项列表清单
copy_pair = zip(source_files,destination_files) #将源目录与备份目录文件拆分成无组
for item in copy_pair:
if os.path.isfile(item[0]):
shutil.copyfile(item[0], item[1])
if __name__ == '__main__':
main()