Python,如何实现类似.gitignore行为的东西

fj1*_*23x 9 python glob pattern-matching gitignore fnmatch

我需要列出当前目录(.)中的所有文件(包括所有子目录),并排除一些文件,如.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))
Run Code Online (Sandbox Code Playgroud)

如何"过滤"并获取与"ignore_files"的一个或多个元素不匹配的所有文件?

谢谢!

aba*_*ert 11

你走在正确的轨道上:如果你想使用fnmatch风格模式,你应该使用fnmatch.filter它们.

但是有三个问题使得这不是微不足道的.

首先,您想要应用多个过滤器.你是怎样做的?filter多次通话:

for ignore in ignore_files:
    filenames = fnmatch.filter(filenames, ignore)
Run Code Online (Sandbox Code Playgroud)

其次,你真正想要做相反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)]
Run Code Online (Sandbox Code Playgroud)

最后,您试图过滤部分路径名,而不仅仅是文件名,但是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)
Run Code Online (Sandbox Code Playgroud)

有几种方法可以改善这一点.

您可能希望使用生成器表达式而不是列表推导(括号而不是方括号),因此如果您有大量文件名列表,则使用惰性管道而不是浪费时间和空间重复构建大型列表.

此外,如果颠倒循环的顺序,它可能会或可能不会更容易理解,如下所示:

filenames = (n for n in filenames 
             if not any(fnmatch(n, ignore) for ignore in ignore_files))
Run Code Online (Sandbox Code Playgroud)

最后,如果您担心性能,可以fnmatch.translate在每个表达式上使用它们将它们转换为等效的regexp,然后将它们合并为一个大的正则表达式并对其进行编译,并使用它而不是循环fnmatch.如果允许您的模式比仅仅更复杂*.jpg,这可能会变得棘手,除非您确实在此确定性能瓶颈,否则我不会推荐它.但是如果你需要这样做,我至少看到过一个关于SO的问题,有人花了很多精力来解决所有边缘情况,所以搜索而不是试图自己编写.

  • 这实际上并不能处理[**。gitignore`规则](http://git-scm.com/docs/gitignore),例如`** / a / b`,`a / b / **`和`a / ** / b`也似乎不会处理简单的`foo`。例如,.gitignore中的`foo`将匹配`foo`和`a / foo`,但fnmatch将在`a / foo`上失败 (2认同)