1.filecmp模块介绍
当我们进行代码审计或校验备份结果时,往往需要检查原始与目 标目录的文件一致性,Python的标准库已经自带了满足此需求的模块 filecmp。filecmp可以实现文件、目录、遍历子目录的差异对比功能。比 如报告中输出目标目录比原始多出的文件或子目录,即使文件同名也会 判断是否为同一个文件(内容级对比)等,Python 2.3或更高版本默认 自带filecmp模块,无需额外安装,下面进行详细介绍。
2.模块常用方法说明
filecmp提供了三个操作方法,分别为cmp(单文件对比)、 cmpfiles(多文件对比)、dircmp(目录对比),下面逐一进行介绍: ·
单文件对比:
采用filecmp.cmp(f1,f2[,shallow])方法,比较文 件名为f1和f2的文件,相同返回True,不相同返回False,shallow默认为 True,意思是只根据os.stat()方法返回的文件基本信息进行对比,比 如最后访问时间、修改时间、状态改变时间等,会忽略文件内容的对 比。当shallow为False时,则os.stat()与文件内容同时进行校验。
示例:比较单文件的差异
>>> import filecmp >>> from filecmp import cmp >>> cmp( ' /home/yhl/devpython/part2/nginx.conf.v1 ' , ' /home/yhl/devpython/part2/nginx.conf.v2 ' ) False >>> cmp( ' /home/yhl/devpython/part2/nginx.conf.v1 ' , ' /home/yhl/devpython/part2/nginx.conf.v0 ' ) True
多文件对比:
采用filecmp.cmpfiles(dir1,dir2,common[, shallow])方法,对比dir1与dir2目录给定的文件清单。该方法返回文件 名的三个列表,分别为匹配、不匹配、错误。匹配为包含匹配的文件的 列表,不匹配反之,错误列表包括了目录不存在文件、不具备读权限或 其他原因导致的不能比较的文件清单
示例:dir1与dir2目录中指定文件清单对比。
两目录下文件的md5信息如下,其中f1、f2文件匹配;f3不匹配; f4、f5对应目录中不存在,无法比较。
创建测试文件:
[yhl@myhost part2]$ mkdir dir1 [yhl@myhost part2]$ mkdir dir2 [yhl@myhost part2]$ cd dir1 [yhl@myhost dir1]$ echo f1 > f1 [yhl@myhost dir1]$ echo f2 > f2 [yhl@myhost dir1]$ echo f3 > f3 [yhl@myhost dir1]$ echo f5 > f5 [yhl@myhost dir1]$ cd .. / dir2 [yhl@myhost dir2]$ echo f1 > f1 [yhl@myhost dir2]$ echo f2 > f2 [yhl@myhost dir2]$ echo f03 > f3 [yhl@myhost dir2]$ echo f4 >f4
对比dir1和dir2目录下文件的md5
[yhl@myhost dir1]$ md5sum * 2b1abc6b6c5c0018851f9f8e6475563b f1 575c5638d60271457e54ab7d07309502 f2 3385b5d27d4c2923e9cde7ea53f28e2b f3 4c89aa650e394e642f6a84df6cdb08a4 f5 [yhl@myhost dir1]$ cd - /home/yhl/devpython/part2/ dir2 [yhl@myhost dir2]$ md5sum * 2b1abc6b6c5c0018851f9f8e6475563b f1 575c5638d60271457e54ab7d07309502 f2 287df2010a083579b709b63445a32cc3 f3 5f3022d3a5cbcbf30a75c33ea39b2622 f4
使用cmpfiles对比的结果如下,符合我们的预期。
f1、f2文件匹配;f3不匹配; f4、f5对应目录中不存在,无法比较。
>>> from filecmp import cmpfiles >>> cmpfiles( " /home/yhl/devpython/part2/dir1 " , " /home/yhl/devpython/part2/dir2 " ,[ ' f1 ' , ' f2 ' , ' f3 ' , ' f4 ' , ' f5 ' ]) ([ ' f1 ' , ' f2 ' ], [ ' f3 ' ], [ ' f4 ' , ' f5 ' ])
目录对比,通过dircmp(a,b[,ignore[,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的目录差异
通过调用dircmp()方法实现目录差异对比功能,同时输出目录 对比对象所有属性信息。
【simple3.py】
# !/usr/bin/python # _*_coding:utf-8_*_ # ****************************************************************# # ScriptName: simple3.py # Author: BenjaminYang # Create Date: 2019-05-13 17:44 # Modify Author: BenjaminYang # Modify Date: 2019-05-13 17:44 # Function: # ***************************************************************# import filecmp a = " /home/yhl/devpython/part2/testfile/dir1 " # 定义左目录 b= " /home/yhl/devpython/part2/testfile/dir2 " # 定义右目录 dir_obj=filecmp.dircmp(a,b,[ ' test.py ' ]) # 目录比较 # 输出对比结果数据报表,详细说明请参考filecmp类方法及属性信息 print ' --------------report比较当前指定目录中的内容---------------- ' dir_obj.report() # 比较当前指定目录中的内容; print ' --------------report_partial_closure比较当前指定目录及第一级子目录中 的内容---------------- ' dir_obj.report_partial_closure() # 比较当前指定目录及第一级子目录中 的内容; print ' --------------report_full_closure递归比较所有指定目录的内容---------------- ' dir_obj.report_full_closure() # 递归比较所有指定目录的内容。 print ' --------------left_list左目录中的文件及目录列表---------------- ' print " left_list: " + str(dir_obj.left_list) # 左目录中的文件及目录表; print ' --------------right_list右目录中的文件及目录列表---------------- ' print " right_list: " + str(dir_obj.right_list) # 右目录中的文件及目录列表; print ' --------------common两边目录共同存在的文件或目录---------------- ' print " common: " + str(dir_obj.common) # 两边目录共同存在的文件或目录 print ' --------------left_only只在左目录中的文件或目录---------------- ' print " left_only: " + str(dir_obj.left_only) # 只在左目录中的文件或目录; print ' --------------right_only右目录中的文件及目录列表---------------- ' print " right_only: " + str(dir_obj.right_only) # 只在右目录中的文件或目录; print ' --------------common_dirs两边目录都存在的子目录---------------- ' print " common_dirs: " + str(dir_obj.common_dirs) # 两边目录都存在的子目录; print ' --------------common_files两边目录都存在的子文件---------------- ' print " common_files: " + str(dir_obj.common_files) # 两边目录都存在的子文件; print ' --------------common_funny两边目录都存在的子目录(不同目录类型或os.stat()记录的错误---------------- ' print " common_funny: " + str(dir_obj.common_funny) # 两边目录都存在的子目录(不同目录类型或os.stat()记录的错误); print ' --------------same_files匹配相同的文件---------------- ' print " same_file: " + str(dir_obj.same_files) # 匹配相同的文件; print ' --------------diff_files不匹配的文件---------------- ' print " diff_files: " + str(dir_obj.diff_files) # 不匹配的文件; print ' --------------funny_files#两边目录中都存在,但无法比较的文件---------------- ' print " funny_files: " + str(dir_obj.funny_files) # 两边目录中都存在,但无法比较的文件
代码输出:
[yhl@myhost testfile]$ python simple3.py --------------report比较当前指定目录中的内容---------------- diff /home/yhl/devpython/part2/testfile/dir1 /home/yhl/devpython/part2/testfile/ dir2 Only in /home/yhl/devpython/part2/testfile/dir1 : [ ' f4 ' ] Only in /home/yhl/devpython/part2/testfile/dir2 : [ ' aa ' , ' f5 ' ] Identical files : [ ' f1 ' , ' f2 ' ] Differing files : [ ' f3 ' ] Common subdirectories : [ ' a ' ] --------------report_partial_closure比较当前指定目录及第一级子目录中 的内容---------------- diff /home/yhl/devpython/part2/testfile/dir1 /home/yhl/devpython/part2/testfile/ dir2 Only in /home/yhl/devpython/part2/testfile/dir1 : [ ' f4 ' ] Only in /home/yhl/devpython/part2/testfile/dir2 : [ ' aa ' , ' f5 ' ] Identical files : [ ' f1 ' , ' f2 ' ] Differing files : [ ' f3 ' ] Common subdirectories : [ ' a ' ] diff /home/yhl/devpython/part2/testfile/dir1/a /home/yhl/devpython/part2/testfile/dir2/ a Identical files : [ ' a1 ' ] Common subdirectories : [ ' b ' ] --------------report_full_closure递归比较所有指定目录的内容---------------- diff /home/yhl/devpython/part2/testfile/dir1 /home/yhl/devpython/part2/testfile/ dir2 Only in /home/yhl/devpython/part2/testfile/dir1 : [ ' f4 ' ] Only in /home/yhl/devpython/part2/testfile/dir2 : [ ' aa ' , ' f5 ' ] Identical files : [ ' f1 ' , ' f2 ' ] Differing files : [ ' f3 ' ] Common subdirectories : [ ' a ' ] diff /home/yhl/devpython/part2/testfile/dir1/a /home/yhl/devpython/part2/testfile/dir2/ a Identical files : [ ' a1 ' ] Common subdirectories : [ ' b ' ] diff /home/yhl/devpython/part2/testfile/dir1/a/b /home/yhl/devpython/part2/testfile/dir2/a/ b Identical files : [ ' b1 ' , ' b2 ' , ' b3 ' ] --------------left_list左目录中的文件及目录列表---------------- left_list: [ ' a ' , ' f1 ' , ' f2 ' , ' f3 ' , ' f4 ' ] --------------right_list右目录中的文件及目录列表---------------- right_list: [ ' a ' , ' aa ' , ' f1 ' , ' f2 ' , ' f3 ' , ' f5 ' ] --------------common两边目录共同存在的文件或目录---------------- common: [ ' a ' , ' f1 ' , ' f2 ' , ' f3 ' ] --------------left_only只在左目录中的文件或目录---------------- left_only: [ ' f4 ' ] --------------right_only右目录中的文件及目录列表---------------- right_only: [ ' aa ' , ' f5 ' ] --------------common_dirs两边目录都存在的子目录---------------- common_dirs: [ ' a ' ] --------------common_files两边目录都存在的子文件---------------- common_files: [ ' f1 ' , ' f2 ' , ' f3 ' ] --------------common_funny两边目录都存在的子目录(不同目录类型或os.stat()记录的错误---------------- common_funny: [] --------------same_files匹配相同的文件---------------- same_file: [ ' f1 ' , ' f2 ' ] --------------diff_files不匹配的文件---------------- diff_files: [ ' f3 ' ] --------------funny_files # 两边目录中都存在,但无法比较的文件---------------- funny_files: []
实践:校验源与备份目录差异
有时候我们无法确认备份目录与源目录文件是否保持一致,包括 源目录中的新文件或目录、更新文件或目录有无成功同步,定期进行校 验,没有成功则希望有针对性地进行补备份。本示例使用了filecmp模块 的left_only、diff_files方法递归获取源目录的更新项,再通过 shutil.copyfile、os.makedirs方法对更新项进行复制,最终保持一致状 态。详细源码如下:
【simple4.py】
# !/usr/bin/python # _*_coding:utf-8_*_ # ****************************************************************# # ScriptName: simple4.py # Author: BenjaminYang # Create Date: 2019-05-14 17:48 # Modify Author: BenjaminYang # Modify Date: 2019-05-14 17:48 # Function: # ***************************************************************# import os, sys import filecmp import re import shutil holderlist = [] def compareme(dir1,dir2): # 递归获取更新函数 dircomp= filecmp.dircmp(dir1,dir2) only_in_one =dircomp.left_only # 源目录新文件或目录 diff_in_one=dircomp.diff_files # 不匹配文件,源目录文件已发生变化 dirpath=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,itme) 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()
[yhl@myhost testfile]$ python simple4.py dir1 dir2 update item: [ ' /home/yhl/devpython/part2/testfile/dir1/f4 ' , ' /home/yhl/devpython/part2/testfile/dir1/f3 ' ] [yhl@myhost testfile]$ python simple4.py dir1 dir2 update item: [] #再次运行时已经没有更新项了