15

我需要列出当前目录 (.) 中的所有文件(包括所有子目录),并排除一些文件作为 .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”的一个或多个元素不匹配的所有文件?

谢谢!

4

2 回答 2

16

你走在正确的轨道上:如果你想使用fnmatch-style 模式,你应该使用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每个表达式将它们转换为等效的正则表达式,然后将它们合并为一个大的正则表达式并编译它,并使用它而不是循环fnmatch。如果您的模式被允许比 更复杂,这可能会变得很棘手*.jpg,除非您确实在这里确定了性能瓶颈,否则我不会推荐它。但是,如果您需要这样做,我已经看到至少一个关于 SO 的问题,有人付出了很多努力来敲定所有边缘情况,因此请搜索而不是尝试自己编写。

于 2014-08-10T16:52:07.910 回答
-1
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
于 2014-08-10T14:36:33.187 回答