在日常运维管理中,经常还有一些文件内容等相关的需求,就可能会有这样的疑问:
1)两个目录中的文件到底有什差别?
2)系统中有多少重复文件存在?
3 )如何找到并删除系统中的重复文件?
在这一篇文章中,将重点介绍如何使用Python解决这几个问题
一、目录和文件比较
filecmp模块包含了比较目录和文件的操作为了对filecmp模块进行测试和验证,我们在当前下创建如下文件和目录:
[root@VM-17-4-centos filecmp_tmp]# tree . ├── dir1 │ ├── a_copy.txt │ ├── a.txt │ ├── b.txt │ ├── c.txt │ └── subdir1 │ └── d.txt └── dir2 ├── a.txt ├── b.txt ├── c.txt └── subdir1 └── e.txt In [1]: import filecmp In [5]: filecmp.cmp('a.txt','b.txt') Out[5]: False In [6]: filecmp.cmp('a.txt','a_copy.txt') Out[6]: True
filecmp下还有个名为cmpfiles的函数,该函数用来同时比较两个不同目录下的多个文件,并且返回一个三元组,分别包含相同的文件、不同的文件和无法比较的文件在测试环境的顶层目录执行以下代码后效果如下:
In [9]: filecmp.cmpfiles('dir1','dir2',['a.txt','b.txt','c.txt','a_copy.txt']) Out[9]: (['c.txt'], ['a.txt', 'b.txt'], ['a_copy.txt'])
cmpfiles函数用来同时比较两个目录下的文件,也可以使用该函数比较两个目录,但是,在比较两个目录时需要通过参数指定所有可能的文件,显然比较繁琐,filecmp中还提供了一个名为dircmp的函数用来比较两个目录。
调用diremp函数以后会返回一个dircmp类的对象,该对象保存了诸多属性,工程师可以通过读取这些属性的方式获取目录之间的差异如下所示:
In [10]: d=filecmp.dircmp('dir1','dir2') In [11]: d.report() diff dir1 dir2 Only in dir1 : ['a_copy.txt'] Identical files : ['c.txt'] Differing files : ['a.txt', 'b.txt'] Common subdirectories : ['subdir1'] In [13]: d.left_list Out[13]: ['a.txt', 'a_copy.txt', 'b.txt', 'c.txt', 'subdir1'] In [14]: d.left_only Out[14]: ['a_copy.txt'] In [15]: d.right_list Out[15]: ['a.txt', 'b.txt', 'c.txt', 'subdir1'] In [16]: d.right_only Out[16]: []
从这里的测试可以看到,filecmp模块的dircmp函数仅仅比较目录下面的文件和子目录,但是,并不会递归比较子目录的内容,对于目录,dircmp函数也仅仅是比较函数的名称,不会去比较子目录里面的内容。例如,这个例子中的dir1/subdir1,dir2/subdir里面的文件完全不同,但是dircmp并不会报它们之间的差异
二、MD5文件校验和比较
前面介绍如何使用filecmp模块对文件和目录进行比较,虽然filecmp比较文件和目录的使用方式比较简单,但它有很多无法处理的情况,例如,找到当前目录和子目录下所有相同的文件,比较不同计算机上的文件是否相同。简单的比较两个文件是否相或者比较两个目录下的文件差异,很多时候并不能满足我们的需求;这个时候,可以通过校验码(checksum)的方式对文件进行比较
校验码是通过散列函数计算而成,是一种从任何数据中创建小的数字“指纹”的方法,散列函数把数据缩成摘要,使得数据量变小,便于进行比较;MD5是目前使用广泛的散列算法,理论上看MD5哈希值可对应无限个文件,但从现实的角度来看,两个不同文件几乎不可能有相同的MD5哈希值,任何对一个文件的非恶意变更都会导致MD哈希值改变,因此,MD5哈希一般用于检查文件完整性,尤其常用于检测文件传磁盘错误或他情况下文件的正确性
In [17]: import hashlib In [18]: d=hashlib.md5() In [20]: with open('/etc/group') as f: ...: for line in f: ...: d.update(line) ...: In [21]: d.hexdigest() Out[21]: '1b76f600aa8008757c921870a8ebe281'
案例:找到目录下的重复文件
接下来看一个综合案例,在这个例子中,我们要找到某个目录下所有的重复文件,先通过find_specific_files函数找到目录下的所有文件,然后通MD5校验判断文件否相同,为了让代码尽可能的通用,我们将计算文件的MD5校验码的功能封装个名为get_file_ checksum的函数,该函数接受文件名作为参数返回文件D5验码功能的现如下所示:
# -*- coding: utf-8 -*- import os import fnmatch import sys import hashlib CHUNK_SIZE = 8192 def is_file_match(filename, patterns): for pattern in patterns: if fnmatch.fnmatch(filename, pattern): return True return False def find_specific_files(root, patterns=['*'], exclude_dirs=[]): for root, dirnames, filenames in os.walk(root): for filename in filenames: if is_file_match(filename, patterns): yield os.path.join(root, filename) for d in exclude_dirs: if d in dirnames: dirnames.remove(d) def get_chunk(filename): with open(filename) as f: while True: chunk = f.read(CHUNK_SIZE) if not chunk: break else: yield chunk def get_file_checksum(filename): h = hashlib.md5() for chunk in get_chunk(filename): h.update(chunk) return h.hexdigest() def main(): sys.argv.append("") directory = sys.argv[1] if not os.path.isdir(directory): raise SystemExit("{0} is not a directory ".format(directory)) record = {} for item in find_specific_files(directory): checksum = get_file_checksum(item) if checksum in record: print 'find duplicate file: {0} vs {1}'.format(record[checksum], item) else: record[checksum] = item if __name__ == '__main__': main()