Python glob多个文件类型

250

在Python中,使用glob.glob获取多个文件类型(例如.txt,.mdown和.markdown)的文件列表有更好的方法吗?目前我的代码是这样的:

projectFiles1 = glob.glob( os.path.join(projectDir, '*.txt') )
projectFiles2 = glob.glob( os.path.join(projectDir, '*.mdown') )
projectFiles3 = glob.glob( os.path.join(projectDir, '*.markdown') )

3
非常相关:https://dev59.com/z1YM5IYBdhLWcg3w7jja - bers
为什么不使用 main_file = projectFiles1 + projectFiles2 + projectFiles3 呢?这样也可以通过连接生成包含所有类型的主列表。 - Delrius Euphoria
从未见过文件*.mdown..;) - Timo
40个回答

235

也许还有更好的方法,但是怎么样:

import glob
types = ('*.pdf', '*.cpp') # the tuple of file types
files_grabbed = []
for files in types:
    files_grabbed.extend(glob.glob(files))

# files_grabbed is the list of pdf and cpp files
也许还有其他方法,所以请耐心等待,以防有人提出更好的答案。

41
files_grabbed = [glob.glob(e) for e in ['*.pdf', '*.cpp']] 的翻译:获取文件列表 = [对于每个后缀名为 '*.pdf' 或 '*.cpp' 的文件,使用 glob.glob() 函数进行搜索] 其中 glob.glob() 是一个Python函数,用于在指定目录下搜索符合指定模式的文件路径,并将结果以列表形式返回。 - Novitoll
26
Novitoll的解决方案简短明了,但最终会创建嵌套列表。 - robroc
19
你可以始终这样做 ;) [f for f_ in [glob.glob(e) for e in ('*.jpg', '*.mp4')] for f in f_] 的翻译是:你总是可以这样做 ;) [f for f_ in [glob.glob(e) for e in ('*.jpg', '*.mp4')] for f in f_] - Alex
9
这段代码会遍历两次文件列表,在第一次迭代中,它会检查所有的*.pdf文件,在第二次迭代中它会检查所有的*.cpp文件。有没有办法只遍历一次就完成检查呢?可以每次检查时都检查这两个条件的组合吗? - Ridhuvarshan
2
如果两个或更多扩展名匹配同一个文件,那么在上述任一解决方案中会发生什么情况?在这种情况下,我们将有重复项需要考虑...我认为任务意味着我们想要每个唯一的文件,因此解决方案应该考虑到这一点。 - niid
显示剩余3条评论

123

glob返回一个列表:为什么不只运行它多次并连接结果?

from glob import glob
project_files = glob('*.txt') + glob('*.mdown') + glob('*.markdown')

9
这可能是最易读的解决方案。我会将 ProjectFiles 的大小写更改为 projectFiles,但是这个方案很棒。 - Hans Goldman
10
请注意,Python 3.x 中的 Path.glob('*') 返回一个生成器,因此您需要在其周围添加 list(...) 才能使用这个技巧。 - Marc Maxmeister
@MarcMaxmeister 不是这样的!glob确实返回一个生成器,但至少在Python 3.5+中,连接起来的效果是正常的。不过我还没有快速测试早期版本的Python的方法。 - patrick-mooney
在 Python 3.10 中,Path().glob("*") + Path().glob("*") 会出现“TypeError: unsupported operand type(s) for +: 'generator' and 'generator'”错误。 - bers
4
@bers 是的,但这是因为Path.glob()glob.glob()的语义不同。我的评论是关于glob.glob()的,它在Python 3.10中可以正常工作:glob.glob('*.md') + glob.glob('*.jpg')在Python 3.10中可以正常工作。在Python 3.7中也是同样的方式:Path.glob()返回一个生成器,但glob.glob()返回一个列表。 - patrick-mooney

83

有很多答案建议按扩展名的数量使用globbing,而我更喜欢只进行一次globbing:

from pathlib import Path

files = (p.resolve() for p in Path(path).glob("**/*") if p.suffix in {".c", ".cc", ".cpp", ".hxx", ".h"})

4
采用扩展集合而非列表来提升性能。 - Elijah
目前最快的答案。您应该使用一组扩展,并且可以更改为Path(path).iterdir()以禁止递归迭代。 - Louis Lac
10
我用纯集合实现和纯列表实现进行了测试,使用8个扩展并搜索了数千个文件,性能上没有显著差异。 - MountainX
@MoutainX 集合在扩展数量达到显著较高的水平(我想是几千个)时开始表现出色,通常人们不会查找那么多的扩展,因此这在这里不会有太大的影响,但这是一个好的实践。 - Louis Lac
@LouisLac - 就整体速度而言,我的测试结果与 https://dev59.com/_G455IYBdhLWcg3wD_oB#56619011 相似 -- 最快的解决方案使用嵌套的 for 循环而不是 glob。像这样 for root, dirs, files in walk(path): for file in files: for ext in extensions: - MountainX

73
from glob import glob

files = glob('*.gif')
files.extend(glob('*.png'))
files.extend(glob('*.jpg'))

print(files)
如果您需要指定路径,请循环匹配模式并将连接保持在循环内以保持简单:
from os.path import join
from glob import glob

files = []
for ext in ('*.gif', '*.png', '*.jpg'):
   files.extend(glob(join("path/to/dir", ext)))

print(files)

最后一个例子很棒。有没有想过如何将其变成递归的? - Arete

48

链接结果:

import itertools as it, glob

def multiple_file_types(*patterns):
    return it.chain.from_iterable(glob.iglob(pattern) for pattern in patterns)

那么:

for filename in multiple_file_types("*.txt", "*.sql", "*.log"):
    # do stuff

19
将 glob.glob 改为 glob.iglob,以便完全惰性评估迭代器链。 - rodrigob
3
我找到了相同的解决方案,但不知道chain.from_iterable。所以这个解决方案类似,但不太易读:it.chain(*(glob.iglob(pattern) for pattern in patterns)) - florisla

38
例如,对于多个文件夹中的 *.mp3*.flac 文件,您可以执行以下操作:
mask = r'music/*/*.[mf][pl][3a]*'
glob.glob(mask)

这个想法可以扩展到更多的文件扩展名,但你必须检查这些组合是否会匹配到那些文件夹中可能存在的其他不需要的文件扩展名。因此,使用时需要小心。

要将任意列表的扩展名自动组合成单个 glob 模式,可以执行以下操作:

def multi_extension_glob_mask(mask_base, *extensions):
    mask_ext = ['[{}]'.format(''.join(set(c))) for c in zip(*extensions)]
    if not mask_ext or len(set(len(e) for e in extensions)) > 1:
        mask_ext.append('*')
    return mask_base + ''.join(mask_ext)

mask = multi_extension_glob_mask('music/*/*.', 'mp3', 'flac', 'wma')
print(mask)  # music/*/*.[mfw][pml][a3]*

这对我也有效:mask = r'music/*/*[mf|pl|3a]' - Jeff Bezos
这对我也起作用:mask = r'music/*/*[mf|pl|3a]' - undefined

22

使用glob模块无法达到此目的。您只能使用以下方式:
* 匹配所有字符
? 匹配任意单个字符
[seq] 匹配seq中的任意字符
[!seq] 匹配不在seq中的任意字符

请使用os.listdir和正则表达式来检查模式:

for x in os.listdir('.'):
  if re.match('.*\.txt|.*\.sql', x):
    print x

12
将正则表达式以 $ 结尾,以匹配文件名的末尾。 - ThiefMaster
2
我喜欢这种方法 - 如果glob的表现力不够强大,就升级到更强大的正则表达式系统,而不是使用例如itertools进行黑客攻击,因为后续模式更改也必须很hacky(比如说您想允许大写和小写)。哦,而且可能更干净的写法是 '.*\.(txt|sql)' - metakermit
1
有没有理由更喜欢使用os.listdir('.')而不是glob.iglob('.')? - Mr.WorshipMe

19

虽然Python的默认glob与Bash的glob并不完全相同,但您可以使用其他库来实现这一点。我们可以在wcmatch的glob中启用花括号。

>>> from wcmatch import glob
>>> glob.glob('*.{md,ini}', flags=glob.BRACE)
['LICENSE.md', 'README.md', 'tox.ini']

如果您更喜欢,甚至可以使用扩展的glob模式:

from wcmatch import glob
>>> glob.glob('*.@(md|ini)', flags=glob.EXTGLOB)
['LICENSE.md', 'README.md', 'tox.ini']

这不带recursive标志。 - Shamoon
@Shamoon 不是,它需要 glob.GLOBSTAR 标志。 - facelessuser
@Shamoon - 递归示例从 path 开始使用 **,例如:glob.glob("**/*.{md,ini}", root_dir=path, flags=glob.GLOBSTAR|glob.BRACE)) - cod3monk3y

13

与@BPL相同的答案(计算效率高),但可以处理任何通配符模式,而不仅仅是扩展名:

import os
from fnmatch import fnmatch

folder = "path/to/folder/"
patterns = ("*.txt", "*.md", "*.markdown")

files = [f.path for f in os.scandir(folder) if any(fnmatch(f, p) for p in patterns)]

这个解决方案既高效又方便。它也与glob 的行为非常相似(请参见 文档)。

请注意,在内置包 pathlib 中更简单:

from pathlib import Path

folder = Path("/path/to/folder")
patterns = ("*.txt", "*.md", "*.markdown")

files = [f for f in folder.iterdir() if any(f.match(p) for p in patterns)]

好的解决方案,谢谢!fnmatch(f, p) 应该实际上是 fnmatch(f.name, p) - 因为 f 是一个 nt.DirEntry 对象,不能被 fnmatch 测试。 - S3DEV
1
谢谢,实际上这个可以不用 .name 就能工作,所以我猜 DirEntry 可以用 fnmatch 进行测试。 - Louis Lac
有趣。我的出了一个错误。不管怎样 - 谢谢! - S3DEV
1
我正在使用Python 3.9,也许在之前的版本中已经修复了这个问题。 - Louis Lac
1
公平竞争。我正在使用的系统仍然是3.5版本。(是的,我知道...) - S3DEV
你可以直接写成 files = [f for f in folder.iterdir() if f.suffix.casefold() in [".txt", ".md", ".markdown"]] - srcLegend

9

这是 Pat 答案的一行列表推导式变体(它还包括您想要在特定项目目录中进行全局搜索):

import os, glob
exts = ['*.txt', '*.mdown', '*.markdown']
files = [f for ext in exts for f in glob.glob(os.path.join(project_dir, ext))]

您需要循环扩展名 (for ext in exts),然后针对每个扩展名使用 glob 模块找出匹配的文件 (for f in glob.glob(os.path.join(project_dir, ext))。

这个解决方案简洁明了,没有任何不必要的嵌套循环、列表推导式或者函数来混淆代码。只有纯粹、表达能力强、Pythonic 的禅意

该解决方案允许您自定义一个exts列表,而无需更新代码即可更改。 (这是一种很好的实践!)

列表推导式与 Laurent 解决方案中使用的相同。但我认为,将单个行提取到单独的函数中通常是不必要的,这就是为什么我提供这个替代解决方案的原因。

额外奖励:

如果您不仅需要搜索单个目录,还需要搜索所有子目录,则可以传递recursive=True并使用多目录通配符** 1:

files = [f for ext in exts 
         for f in glob.glob(os.path.join(project_dir, '**', ext), recursive=True)]

这将调用glob.glob('<project_dir>/**/*.txt', recursive=True)并对每个扩展名执行相同操作。 1 技术上,**通配符仅匹配一个或多个字符,包括正斜杠/(与单数的*通配符不同)。实际上,只需要记住只要在**周围加上正斜杠(路径分隔符),它就匹配零个或多个目录。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接