在一些运维场景中,常常需要比较两个环境中的应用目录结构(是否有文件/目录层面上的增删)以及比较两个环境中同名文件内容的不同(即文件层面上的改)。Python自带了两个内建模块可以很好地完成这个工作,filecmp和difflib。前者主要用于比较目录结构上的不同以及笼统的文件内容比较;后者用于比较两个文件具体内容上的不同。综合使用两个模块可以比较完备地做一次比较。

【filecmp】

  filecmp提供一些方法可以很方便地进行对比两个目录在结构上的不同以及笼统的文件内容上的异同。比如

  filecmp.cmp(f1,f2[,shallow])  用于笼统地比较两个文件内容是否相同,shallow可以指定True或者False,当为True的时候这个方法会把文件的属性(os.stat方法调用看到的信息)也作为比较依据的一部分。整个方法最后返回True或者False告诉调用者两个文件的比较结果。

  filecmp.cmpfiles(d1,d2,common[,shallow])  用于比较两个目录下同名的那些文件是否都相同,common接受一个list或者tuple来表示比较哪些同名的文件。

  除了上面了两个模块的静态方法之外,filecmp中还有一个dircmp类用于更加完备的比较处理工作。

  dircmp类的定义是这么描述的:

    dircmp(a,b,ignore=None,hide=None)
A and B are directories.
IGNORE is a list of names to ignore,
defaults to ['RCS', 'CVS', 'tags'].
HIDE is a list of names to hide,
defaults to [os.curdir, os.pardir].

  在构造完一个dircmp类对象之后,可以调用下面这几个方法来输出对比的信息

  d.report()  只比较当前目录的内容,不涉及当前目录下子目录中内容是否相同。输出的结果是类似于下面这样的样子:

diff testdir1 testdir2
Only in testdir1 : ['subdir1']
Only in testdir2 : ['subdir2']
Identical files : ['same.txt']
Differing files : ['file.txt']

  上面的结果中涉及到了只在目录A中、只在目录B中以及同名内容一致(identical)和同名内容不同(differing)四种类别的结果。其实除此之外还有可能得到同名子目录、因为某些问题而无法直接对比内容的文件(比如文件的内容无法hash)等结果。

  

  d.report_partial_closure()  比较当前目录以及往下一级子目录的内容

  d.report_full_closure()  递归比较当前目录下所有子目录,里面的内容。返回的格式化输出中,会按照各个子目录的不同分别列出对比情况。

  上面的三个方法都是filecmp模块帮助我们格式化好的输出。如果需要获取第一手的比较结果,则应该调用dircmp类的一些其他属性。比如:
  left_list  目录A中的子目录和文件列表,相当于os.listdir

  right_list  目录B中的子目录和文件列表

  left/right_only  仅存在于目录A/B中的子目录和文件

  common  两目录中同名的子目录和文件

  common_files  两目录中同名的子文件,内容不一定相同

  common_dirs  两目录中同名的子目录,内容不一定相同

  common_funny  两目录中同名的不可比较文件

  same_files  两目录中同名且内容相同的子文件

  diff_files  两目录中同名但内容不相同的子文件

  funny_files  两目录中同名但无法比较的子文件

  以上所有属性都是返回了一个List,其中是各个文件/目录的名字

  需要注意的是dircmp类默认只对比当前目录层级,对于想要深入递归地对比后辈目录的话就需要采取一些手段。可以使用dircmp的subdirs这个属性

  subdirs  是一个字典,字典的键是当前dircmp类对象对比的两个目录下同名的子目录(也就是说subdirs.keys()等于是common_dirs),每个键对应的值是另一dircmp对象,而这对象对比的就是两个同名子目录的下的内容了。也就是说不断地调取subdirs这个属性就可以实现递归比较了。虽然在这次应用中我没有采取用subdirs而是稍微麻烦一点采用了判断common_dirs的办法,但是两者原理是一样的。我的尝试待会儿写在下面

  

【difflib】

  difflib深入到文件内部,不仅仅给出“文件内容不相同”级别的提示,而是具体说明了哪些地方不相同。常用于文本文件的比较。可以看出,使用difflib还是需要被对比的文件是可以被hash的。下面的说明将基于文本文件的对比来。

  既然涉及到了详细的文件内容,那么就需要有一种表示给人看的,呈现文件内容对比结果的方式。字符界面上比较常见的方法,是像linux中的diff命令的结果那样。比如:

[root@localhost tmp]# cat file1
this is file1
my name is takanashi
today is a little bit cold
tomorrow is holiday
[root@localhost tmp]# cat file2
this is file2
my name is Takanashi
today is a little bit cold
i wouldnt work tomorrow
[root@localhost tmp]# diff file1 file2
,2c1,
< this is file1
< my name is takanashi
---
> this is file2
> my name is Takanashi
4c4
< tomorrow is holiday
---
> i wouldnt work tomorrow

  关于字符提示的详细说明这里就不多提了,可以参看linux篇的介绍。这里提到主要是想说明,difflib这个模块的一些方法也是会输出这样形式的对比结果的。

  difflib模块给出了一些用于比较文本的类,最简单的一种是Differ

  ■  Differ类

  Differ类有compare方法用于直接比较两段文本,比较的结果是通过类似上面那种表现形式来呈现。比如上面的file1和file2两个文件,通过这样一个脚本来比较:

import difflib

d = difflib.Differ()
with open('file1','r') as file1:
content1 = file1.read().splitlines()
with open('file2','r') as file2:
content2 = file2.read().splitlines() print '\n'.join(d.compare(content1,content2))

  可以注意到,Differ类处理的对象并不是一块文本(或者说字符串)而是一个列表,列表是根据文本字符串通过\n split出来的,这一点也适用于difflib其他一些工具类。所以说difflib其实是基于行的比较。

  compare方法返回的是一个生成器,里面是各行比较的结果。运行上面代码输出是:

- this is file1
?             ^ + this is file2
?             ^ - my name is takanashi
?            ^ + my name is Takanashi
?            ^   today is a little bit cold
- tomorrow is holiday
+ i wouldnt work tomorrow

  两文件的前两句有所不同,比较结果前有'-'号的表示left_only,'+'号表示right_only,此外对于类似的行difflib会做进一步比较找出变更的地方。在相关行下方额外添加一行?开头的行,这行中的^号标识出变更发生的位置。两文件第三行是相同的,所以只输出了一遍,行前空格是为了和上下行对齐。后面两行因为差别较大,被认为是各自独特的行所以没有?开头的那行了。

  另外,如果是B文件中某一行比A文件中的行增加或减少了一些字符,那么在?开头那行里会用-和+来表示增减字符是哪些。比如:

- this a file12
? - + this is a file2
? +++

  

  ■  unified_diff方法

  上面说Differ类会把相同的行打印一次。如果两文件相同部分很多,只有一点不同,那么把相同的行都显示出来,即使只打印一遍也还是有点不好的。而difflib.unified_diff方法可以解决这一个问题。

  unified_diff方法接受n这个参数表名只显示发现不同处上下各n行的内容。相似的方法还有context_diff。不太用所以不详细展开了

  ■  SequenceMatcher类

  这个类首先可以用于指定忽略一些字符的比较。在其构造方法中指定第一个参数是函数对象。这个函数接受一个字符并且经过一定判断后返回True或False。根据这个返回结果类将判断要不要把这个字符计入比较结果。比如s = SequenceMatcher(lambda x : x == ' ','some string A','    some string B')。

  上面这个s可以调用方法s.find_longest_match(ab,ae,bb,be)。这个方法返回的是元组(i,j,k),表示上面比较的字符串A,B中A[ab:ae]与B[bb:be]两部分中可以找到最长公共部分A[i:i+k]和B[j:j+k]。

  这个类另一个NB的地方在于不仅仅可以对比字符串,而可以对比任何序列。比如两个列表的对比,也可以通过它来实现。此时构造方法的第一个参数那个函数对象,接受的就不是一个字符而是一个序列中的元素了。

  

  ■  HtmlDiff类

  这个类是我用的,它在Differ类的基础上将原先字符界面的结果呈现改成了更加友好的html界面显示。一目了然

  构造方法:__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

  tabsize是在html中显示的制表符的空格数量,默认是8但是我觉得太大了,改成2或者4更好看一些。wrapcolumn指定界面上的比较栏中文字最大宽度,超过此宽度会自动换行。默认是None也就是不换行,在碰到有很长的行的时候,页面宽度就会很大。linejunk和charjunk就是和ndiff方法中的相关参数差不多的,两个都是函数对象,用于指出怎么样的行或者怎么样的字符不计入比较。

  HtmlDiff类主要用两个方法,make_table和make_file,两者参数类似,只不过前者返回的是可以组成一个独立html文件的html代码(带<html><head>等标签),而后者是生成一个html表格的代码(从<table>开始)。以make_file为例,其参数是make_file(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])。fromlines和tolines是承载比较内容的两个列表,如上面所说,不是字符串是字符串经过'\n'split过的列表。然后fromdesc和todesc用于指定生成的html中对比栏目中抬头的文字。context参数默认是False,如果设置为True,那么html中只会显示有变化的行上线numlines行数的内容,大部分相同的内容就不重复显示了。

  讲了半天,下面是我对HtmlDiff类的一个使用:

    def check_diff(self, index, wrapcolumn):
file1, file2 = self.differing[index]
with open(file1, 'r') as f:
content1 = f.read().splitlines()
with open(file2, 'r') as f:
content2 = f.read().splitlines()
htmlDiff = HtmlDiff(tabsize=2,wrapcolumn=wrapcolumn)
with open('tmp.html', 'w') as f:
f.write(htmlDiff.make_file(content1, content2, fromdesc=self.dir1, todesc=self.dir2))
webbrowser.open('tmp.html')

  这是部分代码,结合了webbrowser模块之后,可以把生成的HTML对比文件立刻打开,得到的HTML界面大概长这样:

  可以看到是比较友好的界面。

  

  

【Python】 文件目录比较工具filecmp和difflib的更多相关文章

  1. Python 爬虫的工具列表 附Github代码下载链接

    Python爬虫视频教程零基础小白到scrapy爬虫高手-轻松入门 https://item.taobao.com/item.htm?spm=a1z38n.10677092.0.0.482434a6E ...

  2. Python 爬虫的工具列表大全

    Python 爬虫的工具列表大全 这个列表包含与网页抓取和数据处理的Python库.网络 通用 urllib -网络库(stdlib). requests -网络库. grab – 网络库(基于pyc ...

  3. Python 爬虫的工具列表

    Python 爬虫的工具列表 这个列表包含与网页抓取和数据处理的Python库 网络 通用 urllib -网络库(stdlib). requests -网络库. grab – 网络库(基于pycur ...

  4. Python自然语言处理工具小结

    Python自然语言处理工具小结 作者:白宁超 2016年11月21日21:45:26 目录 [Python NLP]干货!详述Python NLTK下如何使用stanford NLP工具包(1) [ ...

  5. python 包管理工具

    python 包管理工具 Python当前的包管理工具链是 easy_install/pip + distribute/setuptools + distutils,显得较为混乱. 而将来的工具链组合 ...

  6. Python 包管理工具解惑

    Python 包管理工具解惑 本文链接:http://zengrong.net/post/2169.htm python packaging 一.困惑 作为一个 Python 初学者,我在包管理上感到 ...

  7. Python包管理工具介绍

    常见的包管理工具及关系 setuptools -->distribute easy_install-->pip 1.distribute distribute是对标准库disutils模块 ...

  8. python包管理工具

    pip 是一个安装和管理 Python 包的工具 , 是 easy_install 的一个替换品.本文将详细说明 安装 pip 的方法和 使用 pip 的一些基本操作如安装.更新和卸载 python ...

  9. Python学习笔记 (1) :python简介、工具、编码及基础运算

    学习背景: 精通一门编程语言并编写出自己喜欢的程序是我多年的梦想,一定要找时间实现.此时想起了高中时的我对编程的兴趣十分浓厚,父母给自己购买了学习机插卡式的,只能敲basic代码,同时学校有386计算 ...

随机推荐

  1. 实战DeviceIoControl 之二:获取软盘/硬盘/光盘的参数

    Q 在MSDN的那个demo中,将设备名换成"A:"取A盘参数,先用资源管理器读一下盘,再运行这个程序可以成功,但换一张盘后就失败:换成"CDROM0"取CDR ...

  2. freemarker中的split字符串分割

    freemarker中的split字符串分割 1.简易说明 split分割:用来根据另外一个字符串的出现将原字符串分割成字符串序列 2.举例说明 <#--freemarker中的split字符串 ...

  3. 异常-----freemarker.core.NonStringException

    一,案例一 1.1.错误描述 <html> <head> <meta http-equiv="content-type" content=" ...

  4. WPF基础篇之移动特效

    前一段时间,在做动画特效的时候,在网上看到了一个水平移动控件的例子.里面用到了RenderTransform特效.在网上查找资料发现了一篇基础的文章: 文章源地址:http://www.ithao12 ...

  5. 【BZOJ4827】【HNOI2017】礼物(FFT)

    [BZOJ4827][HNOI2017]礼物(FFT) 题面 Description 我的室友最近喜欢上了一个可爱的小女生.马上就要到她的生日了,他决定买一对情侣手 环,一个留给自己,一 个送给她.每 ...

  6. 【洛谷2055】【CJOJ2487】【ZJOI2009】 假期的宿舍

    题面 题目描述 学校放假了 · · · · · · 有些同学回家了,而有些同学则有以前的好朋友来探访,那么住宿就是一个问题.比如 A 和 B 都是学校的学生,A 要回家,而 C 来看B,C 与 A 不 ...

  7. [BZOJ1030] [JSOI2007] 文本生成器 (AC自动机 & dp)

    Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版.该软件可以随机生成一些文章―――总是 ...

  8. [BZOJ1007] [HNOI2008] 水平可见直线 (凸包)

    Description 在xoy直角坐标平面上有n条直线L1,L2,...Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见的,否则Li为被覆盖的. 例如,对于直线:L1:y=x ...

  9. wireshark抓包看ECN

    由于实验需要,要统计ECN信息.为了验证拓扑中是否真的有ECN信息,用了wireshark进行抓包查看. 网上找到的相关有用资料有:http://blog.csdn.net/u011414200/ar ...

  10. textarea高度随内容自适应

    最近遇到一个需求,视频名称初始有个生成值,并且支持可以手动修改,修改后名称过长后换行高度随内容增加.刚开始想到用input但是发现input不支持换行.后来用了textarea,用js来控制,下面是实 ...