我需要列出当前目录(。)中的所有文件(包括所有子目录),并排除一些文件,如.gitignore如何工作(http://git-scm.com/docs/gitignore)
使用fnmatch(https://docs.python.org/2/library/fnmatch.html),我将能够使用模式“过滤”文件
ignore_files = ['*.jpg', 'foo/', 'bar/hello*']
matches = []
for root, dirnames, filenames in os.walk('.'):
for filename in fnmatch.filter(filenames, '*'):
matches.append(os.path.join(root, filename))
如何“过滤”并获取与“ignore_files”的一个或多个元素不匹配的所有文件?
谢谢!
你走在正确的轨道上:如果你想使用fnmatch
风格的图案,你应该使用fnmatch.filter
。
但是有三个问题使得这不是微不足道的。
首先,您想要应用多个过滤器。你是怎样做的?多次致电filter
:
for ignore in ignore_files:
filenames = fnmatch.filter(filenames, ignore)
其次,您实际上想要执行filter
的反向:返回不匹配的名称子集。正如文档所述:
它与
[n for n in names if fnmatch(n, pattern)]
相同,但实施效率更高。
所以,要做相反的事情,你只需要投入一个not
:
for ignore in ignore_files:
filenames = [n for n in filenames if not fnmatch(n, ignore)]
最后,您尝试过滤部分路径名,而不仅仅是文件名,但是在过滤之后您才会使用join
。所以切换顺序:
filenames = [os.path.join(root, filename) for filename in filenames]
for ignore in ignore_files:
filenames = [n for n in filenames if not fnmatch(n, ignore)]
matches.extend(filenames)
有几种方法可以改善这一点。
您可能希望使用生成器表达式而不是列表推导(括号而不是方括号),因此如果您有大量文件名列表,则使用惰性管道而不是浪费时间和空间重复构建大型列表。
此外,如果颠倒循环的顺序,它可能会或可能不会更容易理解,如下所示:
filenames = (n for n in filenames
if not any(fnmatch(n, ignore) for ignore in ignore_files))
最后,如果您担心性能,可以在每个表达式上使用fnmatch.translate
将它们转换为等效的regexp,然后将它们合并为一个大的正则表达式并编译它,并使用它而不是围绕fnmatch
的循环。如果允许你的模式比*.jpg
更复杂,这可能会变得棘手,除非你真的确定了这里的性能瓶颈,否则我不会推荐它。但是如果你需要这样做,我至少看到过一个关于SO的问题,有人花了很多精力来解决所有边缘情况,所以搜索而不是试图自己编写。
matches.extend([fn for fn if not filename in ignore_files])
应该为简单的文件名做诀窍,对于忽略模式,例如:
def reject(filename, filter):
""" Takes a filename and a filter to reject files that match."""
if len(filter)==0:
return False
else:
return fnmatch.fnmach(filename, filter[0]) or reject(filename, filter[1:])
matches.extend([os.path.join(root, fn) for fn in filenames if not reject(fn, ignore_files)])
上面将从os.walk中的文件名构建一个列表,检查没有任何过滤器提供匹配 - 检查过滤器,直到没有剩下或找到第一个匹配,所以它应该非常快。
您也可以尝试以下方法:
filenames = set(filenames) # convert to a set
for filter in ignore_files:
filenames = filenames - set(fnmatch.filter(filenames, filter)) # remove the matches
matches.extend([os.path.join(root, fn) for fn in filenames]) # Add to matches