如何使用递归查找文件?

1010
我想要递归列出一个目录中的所有文件。我当前有这样的目录结构:
  • src/main.c
  • src/dir/file1.c
  • src/another-dir/file2.c
  • src/another-dir/nested/files/file3.c

我尝试做了以下事情:

from glob import glob

glob(os.path.join('src','*.c'))

但这只会直接获取 src 子文件夹中的文件,例如我会得到 main.c 但不会得到 file1.cfile2.c 等。

from glob import glob

glob(os.path.join('src','*.c'))
glob(os.path.join('src','*','*.c'))
glob(os.path.join('src','*','*','*.c'))
glob(os.path.join('src','*','*','*','*.c'))

但这显然是有限和笨重的,我该如何正确地做到这一点?


在这种情况下 glob('src/**/*.c') 不能工作吗? - Likith Reddy
28个回答

9

如果有人感兴趣的话,我对前三种提出的方法进行了分析。

我的文件夹中总共有大约 500K 个文件,在其中有 2K 个文件匹配所需的模式。

以下是非常基础的代码:

import glob
import json
import fnmatch
import os
from pathlib import Path
from time import time


def find_files_iglob():
    return glob.iglob("./data/**/data.json", recursive=True)


def find_files_oswalk():
    for root, dirnames, filenames in os.walk('data'):
        for filename in fnmatch.filter(filenames, 'data.json'):
            yield os.path.join(root, filename)

def find_files_rglob():
    return Path('data').rglob('data.json')

t0 = time()
for f in find_files_oswalk(): pass    
t1 = time()
for f in find_files_rglob(): pass
t2 = time()
for f in find_files_iglob(): pass 
t3 = time()
print(t1-t0, t2-t1, t3-t2)

我得到的结果如下:
os_walk:约3.6秒
rglob:约14.5秒
iglob:约16.9秒

平台:Ubuntu 16.04,x86_64(i7核心)。


感谢提供基准测试数据。我在Python 3.9.12上对10k个文件进行了测试,结果与此基准测试相同(os.walk最快),尽管差异不像您的示例那样极端。 - mihow

7

最近我需要恢复扩展名为 .jpg 的图片。我运行了 photorec 并恢复了 4579 个目录和 220 万个文件,这些文件有各种各样的扩展名。通过下面的脚本,我能够在几分钟内选择 50133 个扩展名为 .jpg 的文件:

#!/usr/binenv python2.7

import glob
import shutil
import os

src_dir = "/home/mustafa/Masaüstü/yedek"
dst_dir = "/home/mustafa/Genel/media"
for mediafile in glob.iglob(os.path.join(src_dir, "*", "*.jpg")): #"*" is for subdirectory
    shutil.copy(mediafile, dst_dir)

6

适用于Python 3.5及更高版本

import glob

#file_names_array = glob.glob('path/*.c', recursive=True)
#above works for files directly at path/ as guided by NeStack

#updated version
file_names_array = glob.glob('path/**/*.c', recursive=True)

进一步,您可能需要:
for full_path_in_src in  file_names_array:
    print (full_path_in_src ) # be like 'abc/xyz.c'
    #Full system path of this would be like => 'path till src/abc/xyz.c'

4
你的第一行代码无法查找子目录中的文件。但是如果你在末尾加上 /** ,它就能顺利工作了,像这样:file_names_array = glob.glob('src/**/*.c', recursive=True) - NeStack

6

根据其他答案,这是我的当前工作实现,它可以检索根目录中的嵌套xml文件:

files = []
for root, dirnames, filenames in os.walk(myDir):
    files.extend(glob.glob(root + "/*.xml"))

我真的很喜欢使用Python :)


5

Johan和Bruno提供了非常好的解决方案,满足最低的要求。我刚刚发布了Formic,它实现了Ant FileSet和Globs,可以处理这个问题以及更复杂的情况。你的需求可以用以下方式实现:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name

1
Formic似乎已经被放弃了?!而且它不支持Python 3(https://bitbucket.org/aviser/formic/issue/12/support-python-3) - blueyed

3

使用 glob 模块可以另一种方法来完成这个任务。只需将 rglob 方法设置为一个起始基础目录和一个模式匹配模式,它就会返回一个匹配文件名的列表。

import glob
import os

def _getDirs(base):
    return [x for x in glob.iglob(os.path.join( base, '*')) if os.path.isdir(x) ]

def rglob(base, pattern):
    list = []
    list.extend(glob.glob(os.path.join(base,pattern)))
    dirs = _getDirs(base)
    if len(dirs):
        for d in dirs:
            list.extend(rglob(os.path.join(base,d), pattern))
    return list

3
如果文件存储在远程文件系统或归档文件中,您可以使用fsspec AbstractFileSystem类的实现。例如,要列出zip文件中的所有文件:
from fsspec.implementations.zip import ZipFileSystem
fs = ZipFileSystem("/tmp/test.zip")
fs.glob("/**")  # equivalent: fs.find("/")

或者列出公共可用的 S3 存储桶中的所有文件:

from s3fs import S3FileSystem
fs_s3 = S3FileSystem(anon=True)
fs_s3.glob("noaa-goes16/ABI-L1b-RadF/2020/045/**")  # or use fs_s3.find

如果您的实现应该与文件系统无关,那么您也可以将其用于本地文件系统,这可能会很有趣:

from fsspec.implementations.local import LocalFileSystem
fs = LocalFileSystem()
fs.glob("/tmp/test/**")

其他实现包括Google Cloud、Github、SFTP/SSH、Dropbox和Azure。有关详细信息,请参阅fsspec API文档


3

或者使用列表推导式:

 >>> base = r"c:\User\xtofl"
 >>> binfiles = [ os.path.join(base,f) 
            for base, _, files in os.walk(root) 
            for f in files if f.endswith(".jpg") ] 

2

除了建议的答案,你还可以通过一些懒惰生成和列表推导魔法来实现:

import os, glob, itertools

results = itertools.chain.from_iterable(glob.iglob(os.path.join(root,'*.c'))
                                               for root, dirs, files in os.walk('src'))

for f in results: print(f)

除了适应单行并避免在内存中使用不必要的列表之外,这还有一个好处,就是您可以以类似于 ** 运算符的方式使用它,例如,您可以使用 os.path.join(root, 'some/path/*.c') 来获取 src 所有子目录中具有此结构的所有 .c 文件。

2
这是一个在Python 2.7上工作的代码。作为我的DevOps工作的一部分,我需要编写一个脚本,将标记为live-appName.properties的配置文件移动到appName.properties。可能还有其他扩展名文件,例如live-appName.xml。
以下是可行的代码,它可以在给定目录(嵌套级别)中查找文件,然后将其重命名(移动)为所需的文件名。
def flipProperties(searchDir):
   print "Flipping properties to point to live DB"
   for root, dirnames, filenames in os.walk(searchDir):
      for filename in fnmatch.filter(filenames, 'live-*.*'):
        targetFileName = os.path.join(root, filename.split("live-")[1])
        print "File "+ os.path.join(root, filename) + "will be moved to " + targetFileName
        shutil.move(os.path.join(root, filename), targetFileName)

该函数从主脚本中调用。
flipProperties(searchDir)

希望这能帮助到遇到类似问题的人。

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