在Python自动化运维开发过程中,经常会查找各种文件的需求;那么,这一小节将介绍如何使用Python查找特定类型的文件,包括使用字符串匹配文件名的标准库fnmatch和glob,还会介绍遍历目录树的函数os.walk通过这些函数以及前面介绍的获取文件的属性,可以做很多有用的事情。
一、使用fnmatch找到特定的文件
大部分情况下,使用字符串匹配查找特定的文件就能够满足需求,如果需要更加灵活的字符串匹配,可以使用标准库的fnmatch这个库专门用来进行文件名匹配,支持使用通配符进行字符串匹配。
模式 | 含义 |
* | 匹配所有 |
? | 匹配所有单个字符 |
[seq] | 匹配seq中的任何字符 |
[!seq] | 匹配不出现在seq中的任意字符 |
fnmatch这个库比较简单,只有4个函数,分别fnmatch、fnmatchcase、filter、translate;
其中最常用的是fnmatch函数,各个函数的作用如下:
fnmatch:判断文件名是否符合特定的模式;
fnmatchcase:判断文件名是否符合特定的模式,不区分大小写;
filter:返回输入列表中,符合特定模式的文件名列表;
translate:将通配符模式转换成正则表达式
In [2]: import os In [3]: import fnmatch In [4]: os.listdir('.') Out[4]: ['b1.txt', 'd2.jpg', 'a1.txt', 'tmpdir', 'test.txt', 'testdir', 'c2.jpg'] In [5]: [name for name in os.listdir('.') if fnmatch.fnmatch(name,'*.jpg')] Out[5]: ['d2.jpg', 'c2.jpg'] In [6]: [name for name in os.listdir('.') if fnmatch.fnmatch(name,'[a-c]*')] Out[6]: ['b1.txt', 'a1.txt', 'c2.jpg'] In [7]: [name for name in os.listdir('.') if fnmatch.fnmatch(name,'[a-c]?.txt')] Out[7]: ['b1.txt', 'a1.txt'] In [8]: [name for name in os.listdir('.') if fnmatch.fnmatch(name,'[!a-c]*')] Out[8]: ['d2.jpg', 'tmpdir', 'test.txt', 'testdir'] In [9]: [name for name in os.listdir('.') if fnmatch.fnmatch(name,'[!a-c]*.txt')] Out[9]: ['test.txt']
fnmatchcase函数与fnmatch函数几乎一样,只是在匹配文件名时会忽略文件名中字母的大小写,filter函数与fnmatch函数比较类似,区别在于fnmatch每次对一个文件名进行匹配判断,filter函数每次对一组文件名进行匹配判断。filter函数接受文件名列表为第一个参数,文件名模式为第二个参数,然后以列表的形式返回输入列表中所有符合模式的文件,如下所示:
In [10]: names=os.listdir('.') In [11]: names Out[11]: ['b1.txt', 'd2.jpg', 'a1.txt', 'tmpdir', 'test.txt', 'testdir', 'c2.jpg'] In [12]: fnmatch.filter(names, "[a-c]?.txt") Out[12]: ['b1.txt', 'a1.txt'] In [13]: fnmatch.filter(names, "[!a-c]?.*") Out[13]: ['d2.jpg']
二、使用glob找到特定的文件
目前,我要获取特定类型的文件列表,都是先通过os.listdir获取文件列表,然后通过字符串匹配或者使用fnmatch进行文件名模式匹配进行过滤而在Python中还有更加简单的方式,即使用标准库的glob库
glob的作用相当于os.listdir加上fnmatch使用glob以后,不需要调用os.listdir获取文件列表,直接通过模式匹配即可,如下所示:
In [14]: import glob In [15]: glob.glob('*.txt') Out[15]: ['b1.txt', 'a1.txt', 'test.txt'] In [16]: glob.glob('[a-c]?.jpg') Out[16]: ['c2.jpg'] In [17]: glob.glob('[!a-c]?.jpg') Out[17]: ['d2.jpg']
可以看到,Python非常灵活,仅仅是找到目录下特定的文件类型,我们就已经使用了种不同的方式来匹配文件,分别是字符串后缀匹配,fnmatch模式匹配和glob模式匹配虽然字符串后缀匹配功能有限,但是,由于大部分情况下需求比较简单,Python工程师也对Python的字符串函数比较熟悉,所以成为了使用最广泛的方式如果需要更加灵活的匹配文件名方式,可以使用fnmath和glob
三、使用os.walk遍历目录树
前面的例子都是查找某个目录下的文件并通过模式匹配去选择自己需要的文件类型在实际工作过程中,更有可能遇到的是查找某个目录及其子目录下的所有文件;例如,查找某个目录及其子目录下所有的图片文件,查找某个目录及其子目录下最大的十个文件;对于这类需求,可以使用OS模块的walk函数。walk函数遍历某个目录及其子目录,对于每一个目录,walk返回一个三元组(dirpath, dimames, filenames)。其中,dirpath保存的是当前目录,dimames是当前目录下的子目录列表,filenames是当前目录下的文件列表
下面的代码演示了os.walk函数的用法,使用os.walk函数遍历/data/python/test目录及其子目录,并找到所需要的文件:
# -*- coding: utf-8 -*- import os import fnmatch images = ['*.jpg', '*.png', '*.txt'] matches = [] for root, dirnames, filenames in os.walk(os.path.expanduser("/data/python/test")): for extensions in images: for filename in fnmatch.filter(filenames, extensions): matches.append(os.path.join(root, filename)) print matches
执行结果
[root@VM-17-4-centos python]# python fnmatch_test.py ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt', '/data/python/test/tmpdir/c.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt', '/data/python/test/tmpdir/c.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt', '/data/python/test/tmpdir/c.txt'] ['/data/python/test/d2.jpg', '/data/python/test/c2.jpg', '/data/python/test/b1.txt', '/data/python/test/a1.txt', '/data/python/test/test.txt', '/data/python/test/tmpdir/c.txt', '/data/python/test/testdir/wjq.txt']
案例:找到目录下最大(或最老)的十个文件
前面案例为使用os.walk函数遍历目录并找到目录下的所有相关文件,下面再来看几个更加实际需求
1)找到某个目录及子目录下最大的十个文件;
2)找到某个目录及子目录下最老的十个文件;
3)找到某个目录及子目录下,所有文件名中包含“mysql-bin”的文件
看到这里的需求,最简单的想就是参考前面查找图片的案例,对每一个需求提供个程序,如果是名在校大学生或者是刚毕业的应届生,问题不很大。如果是一名已经工作的工程师,对每个需求提供个程序,恐怕是不合格的。这里的几个需求,虽然表面上看完全不一样,但是它们都有一个共同的需求,即找到某个目录及其子目录下的某种文件。更加通用的需求是,找到某个目录树中,除部分特殊目录以外,其他目录中的某些文件。因此,我们可以先实现这个通用的需求,将这个通用的需求抽象成一个函数,再通过调用这个函数来实现其他需求,这样代码就清晰简单的多
[root@VM-17-4-centos python]# cat common_getfile.py # -*- coding: utf-8 -*- import os import fnmatch 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)
这里定义了一个find_specific_files函数,该函数接受三个参数,分别是查找的根路径,匹配的文件模式列表和需要排除的目录列表。其中,匹配模式列表和排除的目录列表都有默认值(默认情况下找到根路径下的所有文件),有了find_specific_files函数以后,实现任何查找类的需求都非常简单,只需要少量代码就能够实现。例如:
[root@VM-17-4-centos python]# cat getfile_special.py # -*- coding: utf-8 -*- import os import sys import fnmatch sys.path.append("/data/python") from common_getfile import find_specific_files reload(sys) if __name__=='__main__': # 查找目录下的所有文件 for item in find_specific_files("."): print (item) print "=====================================" # 查找目录下的所有图片 patterns = ['*.jpg', '*.png'] for item in find_specific_files("/data/python/test", patterns): print item print "======================================" # 查找目录树中,除testdir目录外其他目录下的txt文件 patterns2 = ['*.txt'] exclude_dirs = ['testdir'] for item in find_specific_files("/data/python/test", patterns2, exclude_dirs): print item
有了find_specific_files这个辅助函数以后,再来看前面的需求就会简单的多。
例如,对于找到某个目录及子目录下最大的十个文件,现在已经能够通过find_specific_files找到某个目录下的所有文件,接下来要做的就是获取文件的大小并按大小排序,排序以后输出最大的十个文件
files = {name: os.path.getsize(name) for name in find_specific_files('/data/mysql/3306')} result = sorted(files.items(), key=lambda d: d[1], reverse=True)[:10] for i, t in enumerate(result, 1): print i, t[0], t[1]
在这个例子中,首先通过宇典推导创建了一个字典,字典的key是找到的文件,字典的value是文件的大小构建出字典以后,使用Python内置的sorted函数对字典进行逆序排序,排序完成以后即可获取最大的十个文件,笔者在MySQL一个实例里面运行,得到的结果如下:
执行结果
1 /data/mysql/3306/log/slow.log 2326899845 2 /data/mysql/3306/data/sql_db/mysql_status_history.ibd 679477248 3 /data/mysql/3306/log/mysql3306.000017 254185093 4 /data/mysql/3306/log/mysql3306.000015 158076218 5 /data/mysql/3306/log/mysql3306.000016 97897977 6 /data/mysql/3306/data/ibdata1 79691776 7 /data/mysql/3306/data/ib_logfile0 50331648 8 /data/mysql/3306/data/ib_logfile1 50331648 9 /data/mysql/3306/data/wordpress/wp_posts.ibd 15728640 10 /data/mysql/3306/data/ibtmp1 12582912
可以看到,有了find_specific_files这个辅助函数,以后要实现查找类的功能非常简单,下面再看几个例子
1)找到某个目录及子目录下最老的十个文件:
files = {name: os.path.getmtime(name) for name in find_specific_files('/data/mysql/3306')} result = sorted(files.items(), key=lambda d: d[1], reverse=True)[:10] for i, t in enumerate(result, 1): print i, t[0], time.ctime(t[1])
执行结果
1 /data/mysql/3306/data/ib_logfile0 Wed Mar 3 18:41:22 2021 2 /data/mysql/3306/data/mysql/innodb_index_stats.ibd Wed Mar 3 18:41:20 2021 3 /data/mysql/3306/data/ibdata1 Wed Mar 3 18:41:20 2021 4 /data/mysql/3306/data/mysql/innodb_table_stats.ibd Wed Mar 3 18:41:20 2021 5 /data/mysql/3306/data/sql_db/mysql_status.ibd Wed Mar 3 18:41:10 2021 6 /data/mysql/3306/data/sql_db/mysql_status_history.ibd Wed Mar 3 18:41:10 2021 7 /data/mysql/3306/log/mysql3306.000017 Wed Mar 3 18:41:09 2021 8 /data/mysql/3306/log/slow.log Wed Mar 3 18:41:09 2021 9 /data/mysql/3306/data/sql_db/mysql_repl_status.ibd Wed Mar 3 18:41:08 2021 10 /data/mysql/3306/data/ibtmp1 Wed Mar 3 18:40:10 2021
2)找到某个目录及子目录下,所有文件名中包含“mysql3306”的文件
files = [name for name in find_specific_files('/data/mysql/3306', ['mysql3306*'])] for i, name in enumerate(files, 1): print i, name
执行结果
1 /data/mysql/3306/log/mysql3306.000017 2 /data/mysql/3306/log/mysql3306.000016 3 /data/mysql/3306/log/mysql3306.index 4 /data/mysql/3306/log/mysql3306.000015 5 /data/mysql/3306/tmp/mysql3306.pid
那么有了fnmatch、glob模块,对于文件的查找就方便了许多;
相关阅读