• python运维常用模块(五)——文件目录对比模块filecmp


    1.filecmp模块介绍

    当我们进行代码审计或校验备份结果时,往往需要检查原始与目标目录的文件一致性,Python的标准库已经自带了满足此需求的模块filecmp。filecmp可以实现文件、目录、遍历子目录的差异对比功能。比如报告中输出目标目录比原始多出的文件或子目录,即使文件同名也会判断是否为同一个文件(内容级对比)等,Python2.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('/root/nginx.conf.v1','/root/nginx.conf.v2')
    False
    >>> cmp('/root/nginx.conf.v1','/root/nginx.conf.v1')
    True
    >>> cmp('/root/passwd','/etc/passwd')               
    False
    
    多文件对比:

    采用filecmp.cmpfiles(dir1,dir2,common[, shallow])方法,对比dir1与dir2目录给定的文件清单。该方法返回文件名的三个列表,分别为匹配、不匹配、错误。匹配为包含匹配的文件的列表,不匹配反之,错误列表包括了目录不存在文件、不具备读权限或其他原因导致的不能比较的文件清单。
    示例:dir1与dir2目录中指定文件清单对比。
    两目录下文件的md5信息如下,其中f1、f2文件匹配;f3不匹配; f4、f5对应目录中不存在,无法比较。
    创建测试文件并对比dir1和dir2目录下文件的md5

    [root@prometheus01 ~/dir1]# md5sum *
    2b1abc6b6c5c0018851f9f8e6475563b  f1
    575c5638d60271457e54ab7d07309502  f2
    3385b5d27d4c2923e9cde7ea53f28e2b  f3
    5f3022d3a5cbcbf30a75c33ea39b2622  f4
    
    [root@prometheus01 ~/dir2]# md5sum *      
    2b1abc6b6c5c0018851f9f8e6475563b  f1
    575c5638d60271457e54ab7d07309502  f2
    287df2010a083579b709b63445a32cc3  f3
    4c89aa650e394e642f6a84df6cdb08a4  f5
    

    使用cmpfiles对比的结果如下,符合我们的预期。
    f1、f2文件匹配;f3不匹配;f4、f5对应目录中不存在,无法比较

    >>> from filecmp import cmpfiles
    >>> cmpfiles("/root/dir1","/root/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()方法实现目录差异对比功能,同时输出目录,对比对象所有属性信息。

    #!/usr/bin/python3
    #_*_coding:utf-8_*_
    
    import filecmp
    # 定义左目录
    a="/root/dir1"
    # 定义右目录
    b="/root/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两边中的文件及目录列表---------------------')
    # 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()记录的错误)---------------------')
    # 两边目录都存在的子目录(不同目录类型或os.stat()记录的错误
    print("common_funny:"+ str(dir_obj.common_funny))
    
    print('---------------------same_files匹配相同的文件---------------------')
    # 配相同的文件
    print("same_files:"+ 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))
    

    目录结构如下:

    [root@prometheus01 ~]# tree dir1
    dir1
    ├── a
    │   ├── a1
    │   └── b
    │       ├── b1
    │       ├── b2
    │       └── b3
    ├── f1
    ├── f2
    ├── f3
    ├── f4
    └── test.py
    
    3 directories, 8 files
    [root@prometheus01 ~]# tree dir2
    dir2
    ├── a
    │   ├── a1
    │   └── b
    │       ├── b1
    │       ├── b2
    │       └── b3
    ├── aa
    │   └── aa1
    ├── f1
    ├── f2
    ├── f3
    ├── f5
    └── test.py
    
    4 directories, 9 files
    

    执行结果:

    [root@prometheus01 ~]# python3 simple9.py 
    ---------------------report比较当前指定目录中的内容---------------------
    diff /root/dir1 /root/dir2
    Only in /root/dir1 : ['f4']
    Only in /root/dir2 : ['aa', 'f5']
    Identical files : ['f1', 'f2']
    Differing files : ['f3']
    Common subdirectories : ['a']
    ---------------------report_partial_closure比较当前指定目录及第一级子目录中 的内容---------------------
    diff /root/dir1 /root/dir2
    Only in /root/dir1 : ['f4']
    Only in /root/dir2 : ['aa', 'f5']
    Identical files : ['f1', 'f2']
    Differing files : ['f3']
    Common subdirectories : ['a']
    
    diff /root/dir1/a /root/dir2/a
    Common subdirectories : ['a1', 'b']
    ---------------------report_full_closure递归比较所有指定目录的内容---------------------
    diff /root/dir1 /root/dir2
    Only in /root/dir1 : ['f4']
    Only in /root/dir2 : ['aa', 'f5']
    Identical files : ['f1', 'f2']
    Differing files : ['f3']
    Common subdirectories : ['a']
    
    diff /root/dir1/a /root/dir2/a
    Common subdirectories : ['a1', 'b']
    
    diff /root/dir1/a/a1 /root/dir2/a/a1
    
    diff /root/dir1/a/b /root/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_files:['f1', 'f2']
    ---------------------diff_files不匹配的文件---------------------
    diff_files:['f3']
    ---------------------funny_files两边目录中都存在,但无法比较的文件---------------------
    funny_files:[]
    
    实践:校验源与备份目录差异

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

    #!/usr/bin/python3
    #_*_coding:utf-8_*_
    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()
    

    执行结果:

    [root@prometheus01 ~]# python simple10.py dir1 dir2
    update item: 
    ['/root/dir1/f4', '/root/dir1/f3']
    # 再次运行时已经没有更新项了
    [root@prometheus01 ~]# python simple10.py dir1 dir2
    update item: 
    []
    
  • 相关阅读:
    第二次冲刺-个人总结01
    构建之法阅读笔记03
    第一次冲刺-个人总结07
    第十四周总结
    第一次冲刺-个人总结07
    第一次冲刺-个人总结06
    第一次冲刺-个人总结05
    mysql优化
    springmvc常用注解标签详解
    弄懂JDK、JRE和JVM到底是什么
  • 原文地址:https://www.cnblogs.com/even160941/p/16078464.html
Copyright © 2020-2023  润新知