《Python自动化运维:技术与最佳实践》一2.2 文件与目录差异对比方法

简介:

本节书摘来自华章出版社《Python自动化运维:技术与最佳实践》一书中的第2章,第2.2节,作者 (美)Neil Bergman ,更多章节内容可以访问云栖社区“华章计算机”公众号查看

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

当我们进行代码审计或校验备份结果时,往往需要检查原始与目标目录的文件一致性,Python的标准库已经自带了满足此需求的模块filecmp。filecmp可以实现文件、目录、遍历子目录的差异对比功能。比如报告中输出目标目录比原始多出的文件或子目录,即使文件同名也会判断是否为同一个文件(内容级对比)等,Python 2.3或更高版本默认自带filecmp模块,无需额外安装,下面进行详细介绍。

2.2.1 模块常用方法说明

filecmp提供了三个操作方法,分别为cmp(单文件对比)、cmpfiles(多文件对比)、dircmp(目录对比),下面逐一进行介绍:
单文件对比,采用filecmp.cmp(f1, f2[, shallow])方法,比较文件名为f1和f2的文件,相同返回True,不相同返回False,shallow默认为True,意思是只根据os.stat()方法返回的文件基本信息进行对比,比如最后访问时间、修改时间、状态改变时间等,会忽略文件内容的对比。当shallow为False时,则os.stat()与文件内容同时进行校验。
示例:比较单文件的差异。

>>> filecmp.cmp("/home/test/filecmp/f1","/home/test/filecmp/f3")
True
>>> filecmp.cmp("/home/test/filecmp/f1","/home/test/filecmp/f2")
False

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

[root@SN2013-08-020 dir2]# md5sum *        
d9dfc198c249bb4ac341198a752b9458  f1
aa9aa0cac0ffc655ce9232e720bf1b9f  f2
33d2119b71f717ef4b981e9364530a39  f3
d9dfc198c249bb4ac341198a752b9458  f5
 [root@SN2013-08-020 dir1]# md5sum *  
d9dfc198c249bb4ac341198a752b9458  f1
aa9aa0cac0ffc655ce9232e720bf1b9f  f2
d9dfc198c249bb4ac341198a752b9458  f3
410d6a485bcf5d2d2d223f2ada9b9c52  f4

使用cmpfiles对比的结果如下,符合我们的预期。

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

import filecmp
a="/home/test/filecmp/dir1"    #定义左目录
b="/home/test/filecmp/dir2"    #定义右目录
dirobj=filecmp.dircmp(a,b,['test.py'])    #目录比较,忽略test.py文件
#输出对比结果数据报表,详细说明请参考filecmp类方法及属性信息
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)

为方便理解,通过tree命令输出两个目录的树结构,如图2-4所示。

image

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

# python simple1.py 
-------------------report---------------------
diff /home/test/filecmp/dir1 /home/test/filecmp/dir2
Only in /home/test/filecmp/dir1 : ['f4']
Only in /home/test/filecmp/dir2 : ['aa', 'f5']
Identical files : ['f1', 'f2']
Differing files : ['f3']
Common subdirectories : ['a']
-------------report_partial_closure-----------
diff /home/test/filecmp/dir1 /home/test/filecmp/dir2
Only in /home/test/filecmp/dir1 : ['f4']
Only in /home/test/filecmp/dir2 : ['aa', 'f5']
Identical files : ['f1', 'f2']
Differing files : ['f3']
Common subdirectories : ['a']
diff /home/test/filecmp/dir1/a /home/test/filecmp/dir2/a
Identical files : ['a1']
Common subdirectories : ['b']
-------------report_full_closure--------------
diff /home/test/filecmp/dir1 /home/test/filecmp/dir2
Only in /home/test/filecmp/dir1 : ['f4']
Only in /home/test/filecmp/dir2 : ['aa', 'f5']
Identical files : ['f1', 'f2']
Differing files : ['f3']
Common subdirectories : ['a']
diff /home/test/filecmp/dir1/a /home/test/filecmp/dir2/a
Identical files : ['a1']
Common subdirectories : ['b']
diff /home/test/filecmp/dir1/a/b /home/test/filecmp/dir2/a/b
Identical files : ['b1', 'b2', 'b3']
left_list:['a', 'f1', 'f2', 'f3', 'f4']
right_list:['a', 'aa', 'f1', 'f2', 'f3', 'f5']
common:['a', 'f1', 'f2', 'f3']
left_only:['f4']
right_only:['aa', 'f5']
common_dirs:['a']
common_files:['f1', 'f2', 'f3']
common_funny:[]
same_file:['f1', 'f2']
diff_files:['f3']
funny_files:[]

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

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

#!/usr/bin/env python
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, 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中的f4、code/f3文件后,运行程序结果如下:

# python simple2.py /home/test/filecmp/dir1 /home/test/filecmp/dir2
update item:
['/home/test/filecmp/dir1/f4', '/home/test/filecmp/dir1/code/f3']
# python simple2.py /home/test/filecmp/dir1 /home/test/filecmp/dir2
update item:

[] #再次运行时已经没有更新项了

相关文章
|
8天前
|
运维
【运维基础知识】用dos批处理批量替换文件中的某个字符串(本地单元测试通过,部分功能有待优化,欢迎指正)
该脚本用于将C盘test目录下所有以t开头的txt文件中的字符串“123”批量替换为“abc”。通过创建批处理文件并运行,可实现自动化文本替换,适合初学者学习批处理脚本的基础操作与逻辑控制。
103 56
|
4天前
|
安全 Linux 数据安全/隐私保护
python知识点100篇系列(15)-加密python源代码为pyd文件
【10月更文挑战第5天】为了保护Python源码不被查看,可将其编译成二进制文件(Windows下为.pyd,Linux下为.so)。以Python3.8为例,通过Cython工具,先写好Python代码并加入`# cython: language_level=3`指令,安装easycython库后,使用`easycython *.py`命令编译源文件,最终生成.pyd文件供直接导入使用。
python知识点100篇系列(15)-加密python源代码为pyd文件
|
9天前
|
Linux 区块链 Python
Python实用记录(十三):python脚本打包exe文件并运行
这篇文章介绍了如何使用PyInstaller将Python脚本打包成可执行文件(exe),并提供了详细的步骤和注意事项。
22 1
Python实用记录(十三):python脚本打包exe文件并运行
|
2天前
|
Java Python
> python知识点100篇系列(19)-使用python下载文件的几种方式
【10月更文挑战第7天】本文介绍了使用Python下载文件的五种方法,包括使用requests、wget、线程池、urllib3和asyncio模块。每种方法适用于不同的场景,如单文件下载、多文件并发下载等,提供了丰富的选择。
|
3天前
|
数据安全/隐私保护 流计算 开发者
python知识点100篇系列(18)-解析m3u8文件的下载视频
【10月更文挑战第6天】m3u8是苹果公司推出的一种视频播放标准,采用UTF-8编码,主要用于记录视频的网络地址。HLS(Http Live Streaming)是苹果公司提出的一种基于HTTP的流媒体传输协议,通过m3u8索引文件按序访问ts文件,实现音视频播放。本文介绍了如何通过浏览器找到m3u8文件,解析m3u8文件获取ts文件地址,下载ts文件并解密(如有必要),最后使用ffmpeg合并ts文件为mp4文件。
|
8天前
|
运维 监控 网络安全
自动化运维的魔法:如何用Python简化日常任务
【10月更文挑战第9天】在数字时代的浪潮中,运维人员面临着日益增长的挑战。本文将揭示如何通过Python脚本实现自动化运维,从而提高效率、减少错误,并让运维工作变得更具创造性。我们将探索一些实用的代码示例,这些示例将展示如何自动化处理文件、监控系统性能以及管理服务器配置等常见运维任务。准备好让你的运维工作升级换代了吗?让我们开始吧!
|
9天前
|
JSON 数据格式 Python
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
这篇文章介绍了一个Python脚本,用于统计TXT或JSON文件中特定单词的出现次数。它包含两个函数,分别处理文本和JSON文件,并通过命令行参数接收文件路径、目标单词和文件格式。文章还提供了代码逻辑的解释和示例用法。
22 0
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
|
9天前
|
Python
Python实用记录(十二):文件夹下所有文件重命名以及根据图片路径保存到新路径下保存
这篇文章介绍了如何使用Python脚本对TTK100_VOC数据集中的JPEGImages文件夹下的图片文件进行批量重命名,并将它们保存到指定的新路径。
24 0
|
10天前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
32 4
|
6天前
|
存储 运维 监控
高效运维:从基础架构到自动化管理的全面指南
【10月更文挑战第11天】 本文将深入探讨如何通过优化基础架构和引入自动化管理来提升企业IT运维效率。我们将从服务器的选择与配置、存储解决方案的评估,到网络的设计与监控,逐一解析每个环节的关键技术点。同时,重点讨论自动化工具在现代运维中的应用,包括配置管理、持续集成与部署(CI/CD)、自动化测试及故障排除等方面。通过实际案例分析,展示这些技术如何协同工作,实现高效的运维管理。无论是IT初学者还是经验丰富的专业人员,都能从中获得有价值的见解和实操经验。
17 1