2.2 文件目录差异对比方法 filecmp

2.2 文件目录差异对比方法 filecmp

当我们进行代码审计或校验备份结果时,往往需要检查原始与目标目录的文件一致性,python的标准库自带了满足此需求的模块filecmp,filecmp可以实现文件,目录,遍历子目录的差异对比功能,比如报告中输出目标目录比原始多出的文件或子目录,即使文件同名也会判断是否为同一个文件(内容级别对比)等

2.2.1 模块常用方法说明
filecmp提供了三个操作方法,分别为cmp(单文件对比),cmpfiles(多文件对比),dircmp(目录对比),下面逐一进行介绍:

1).单文件对比.
采用filecmp.cmp(f1,f2[,shallow])方法,比较文件名为f1和f2的文件,相同返回True,不相同返回False,shallow默认为True,意思是只根据os.stat()方法返回的文件基本信息进行比对,比如最后访问时间,修改时间,状态改变时间,会忽略文件内容的对比,当shallow为False时,则os.stat与文件内容同时进行校验

示例:比较单文件的差异

>>> import filecmp
>>> filecmp.cmp("/data/py/nginx.conf.v1","/data/py/nginx.conf.v2")
False
>>> filecmp.cmp("/data/py/nginx.conf.v1","/data/py/nginx.conf.v3")
True

2).多文件对比

采用filecmp.cmpfiles(dir1,dir2,common[,shallow])方法,对比dir1与dir2目录给定的文件清单,该方法返回文件名的三个列表,分别为匹配.不匹配.错误,匹配为包含匹配的文件的列表,不匹配反之,错误列表包括了目录不存在文件,不具备读权限或其他原因导致不能比较的文件清单

示例:dir1与dir2目录中指定文件清单对比
两目录下文件的md5信息如下

[root@mankel dir1] md5sum *
96a97d44f714d7409730663bda8d8b9b  nginx.conf.v1
3db16e2b089ddb31d2d26f1511165a53  nginx.conf.v2
96a97d44f714d7409730663bda8d8b9b  nginx.conf.v3
6eb91b5d156d9868a05b354d0693bf8f  test.html
[root@mankel dir2] md5sum *
96a97d44f714d7409730663bda8d8b9b  nginx.conf.v1
3db16e2b089ddb31d2d26f1511165a53  nginx.conf.v2
be407f6425455084d763954ae594129f  nginx.conf.v3
101fbae1e6c621310c72f2a6e4a9b04e  test2.html
使用cmpfiles对比的结果如下,符合我们的预期:
>>> import filecmp
>>> common_list = ["nginx.conf.v1","nginx.conf.v2","nginx.conf.v3","test.html","test2.html"]
>>> match, mismatch, errors = filecmp.cmpfiles("/data/dir1","/data/dir2",common_list)
>>> print("比较的结果中,匹配的是:\n{}\n不匹配的是:\n{}\n错误的是:\n{}\n".format(match, mismatch, errors))
比较的结果中,匹配的是:
['nginx.conf.v1', 'nginx.conf.v2']
不匹配的是:
['nginx.conf.v3']
错误的是:
['test.html', 'test2.html']

3).目录对比

通过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()方法实现目录差异对比功能,同时输出目录对比对象所有属性信息

[root@mankel py] vim test-1124.py
#!/usr/bin/python3
import filecmp
a = "/data/dir1"
b = "/data/dir2"
dirobj = filecmp.dircmp(a,b,['test-1124.py'])
print("-------------report-------------")
dirobj.report()
print("\n-------------report_partial_closure-------------")
dirobj.report_partial_closure()
print("\n-------------report_full_closure-------------")
dirobj.report_full_closure()
print("\n--------------------------")
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_funnt:"+str(dirobj.common_funny))
print("same_files:"+str(dirobj.same_files))
print("diff_files:"+str(dirobj.diff_files))
print("funny_files:"+str(dirobj.funny_files))

为方便理解,通过tree命令输出两个目录的树结构

[root@mankel py] tree /data/dir1
/data/dir1
├── a
│   ├── a1
│   └── b
│       ├── b1
│       ├── b2
│       └── b3
├── nginx.conf.v1
├── nginx.conf.v2
├── nginx.conf.v3
├── test-1124.py
└── test.html

[root@mankel py] tree /data/dir2
/data/dir2
├── a
│   ├── a1
│   └── b
│       ├── b1
│       ├── b2
│       └── b3
├── aa
│   └── aa1
├── nginx.conf.v1
├── nginx.conf.v2
├── nginx.conf.v3
├── test-1124.py
└── test2.html

运行前面的代码输出,结果如下

[root@mankel py] python3 test-1124.py 
-------------report-------------
diff /data/dir1 /data/dir2
Only in /data/dir1 : ['test.html']
Only in /data/dir2 : ['aa', 'test2.html']
Identical files : ['nginx.conf.v1', 'nginx.conf.v2']
Differing files : ['nginx.conf.v3']
Common subdirectories : ['a']

-------------report_partial_closure-------------
diff /data/dir1 /data/dir2
Only in /data/dir1 : ['test.html']
Only in /data/dir2 : ['aa', 'test2.html']
Identical files : ['nginx.conf.v1', 'nginx.conf.v2']
Differing files : ['nginx.conf.v3']
Common subdirectories : ['a']

diff /data/dir1/a /data/dir2/a
Common subdirectories : ['a1', 'b']

-------------report_full_closure-------------
diff /data/dir1 /data/dir2
Only in /data/dir1 : ['test.html']
Only in /data/dir2 : ['aa', 'test2.html']
Identical files : ['nginx.conf.v1', 'nginx.conf.v2']
Differing files : ['nginx.conf.v3']
Common subdirectories : ['a']

diff /data/dir1/a /data/dir2/a
Common subdirectories : ['a1', 'b']

diff /data/dir1/a/a1 /data/dir2/a/a1

diff /data/dir1/a/b /data/dir2/a/b
Common subdirectories : ['b1', 'b2', 'b3']

diff /data/dir1/a/b/b1 /data/dir2/a/b/b1

diff /data/dir1/a/b/b2 /data/dir2/a/b/b2

diff /data/dir1/a/b/b3 /data/dir2/a/b/b3

--------------------------
left_list:['a', 'nginx.conf.v1', 'nginx.conf.v2', 'nginx.conf.v3', 'test.html']
right_list:['a', 'aa', 'nginx.conf.v1', 'nginx.conf.v2', 'nginx.conf.v3', 'test2.html']
common:['a', 'nginx.conf.v1', 'nginx.conf.v2', 'nginx.conf.v3']
left_only:['test.html']
right_only:['aa', 'test2.html']
common_dirs:['a']
common_files:['nginx.conf.v1', 'nginx.conf.v2', 'nginx.conf.v3']
common_funnt:[]
same_files:['nginx.conf.v1', 'nginx.conf.v2']
diff_files:['nginx.conf.v3']
funny_files:[]

2.2.2 实践:校验源与备份目录差异

有时候我们无法确认备份目录与源目录文件是否保持一致,包括源目录中的新文件或目录.更新文件或目录有无成功同步,定期进行校验,没有成功则希望有针对性地进行补备份,本示例使用了filecmp模块的left_only,diff_files方法递归获取源目录的更新项,再通过shutil.copyfile,os.makedirs方法对更新项进行复制,最终保持一致状态,详细源码如下:

[root@mankel py] vim test.py 
#!/usr/bin/python3
import os
import 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.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()

更新源目录dir1中的文件后,运行程序结果如下:

[root@mankel py] python3 test.py /data/dir1 /data/dir2 
update item:
['/data/dir1/test.html', '/data/dir1/nginx.conf.v3']
[root@mankel py] python3 test.py /data/dir1 /data/dir2 
update item:
[]       #再次运行时已经没有更新项了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值