如何进行递归子文件夹搜索并返回列表中的文件?
Posted
技术标签:
【中文标题】如何进行递归子文件夹搜索并返回列表中的文件?【英文标题】:How to do a recursive sub-folder search and return files in a list? 【发布时间】:2013-08-25 23:49:18 【问题描述】:我正在编写一个脚本,以递归方式遍历主文件夹中的子文件夹,并根据某种文件类型构建一个列表。我的脚本有问题。目前设置如下:
for root, subFolder, files in os.walk(PATH):
for item in files:
if item.endswith(".txt") :
fileNamePath = str(os.path.join(root,subFolder,item))
问题在于subFolder
变量正在拉入子文件夹列表,而不是 ITEM 文件所在的文件夹。我之前正在考虑为子文件夹运行一个 for 循环并加入路径的第一部分,但我想我会仔细检查是否有人在此之前有任何建议。
【问题讨论】:
【参考方案1】:您应该使用您称之为root
的dirpath
。提供了dirnames
,因此如果存在您不希望os.walk
递归到的文件夹,您可以对其进行修剪。
import os
result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(PATH) for f in filenames if os.path.splitext(f)[1] == '.txt']
编辑:
在最近一次投票后,我突然想到glob
是一个更好的按扩展选择的工具。
import os
from glob import glob
result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]
也是一个生成器版本
from itertools import chain
result = (chain.from_iterable(glob(os.path.join(x[0], '*.txt')) for x in os.walk('.')))
Edit2 for Python 3.4+
from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))
【讨论】:
'*.[Tt][Xx][Tt]' 全局模式将使搜索不区分大小写。 @SergiyKolesnikov,谢谢,我在底部的编辑中使用了它。请注意,rglob
在 Windows 平台上是不敏感的 - 但它不是可移植的。
@JohnLaRooy 它也适用于 glob
(此处为 Python 3.6):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
@Sergiy:您的iglob
不适用于子文件夹或以下文件夹中的文件。您需要添加recursive=True
。
@user136036,“更好”并不总是意味着最快。有时可读性和可维护性也很重要。【参考方案2】:
Python 3.5 中的更改:支持使用“**”的递归 glob。
glob.glob()
得到了一个新的recursive parameter。
如果您想获取my_path
下的每个.txt
文件(递归包括子目录):
import glob
files = glob.glob(my_path + '/**/*.txt', recursive=True)
# my_path/ the dir
# **/ every file and dir under my_path
# *.txt every file that ends with '.txt'
如果你需要一个迭代器,你可以使用iglob 作为替代:
for file in glob.iglob(my_path, recursive=True):
# ...
【讨论】:
TypeError: glob() got an unexpected keyword argument 'recursive' 它应该可以工作。确保使用 >= 3.5 的版本。我在答案中添加了指向文档的链接以获取更多详细信息。 这就是为什么,我在 2.7 为什么列表理解,而不仅仅是files = glob.glob(PATH + '/*/**/*.txt', recursive=True)
?
哎呀! :) 这完全是多余的。不知道是什么让我这样写。感谢您提及!我会解决的。【参考方案3】:
这不是最pythonic的答案,但我会把它放在这里是为了好玩,因为它是关于递归的一堂简洁的课
def find_files( files, dirs=[], extensions=[]):
new_dirs = []
for d in dirs:
try:
new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
except OSError:
if os.path.splitext(d)[1] in extensions:
files.append(d)
if new_dirs:
find_files(files, new_dirs, extensions )
else:
return
在我的机器上,我有两个文件夹,root
和 root2
mender@multivax ]ls -R root root2
root:
temp1 temp2
root/temp1:
temp1.1 temp1.2
root/temp1/temp1.1:
f1.mid
root/temp1/temp1.2:
f.mi f.mid
root/temp2:
tmp.mid
root2:
dummie.txt temp3
root2/temp3:
song.mid
假设我想在这两个目录中找到所有.txt
和所有.mid
文件,那么我可以这样做
files = []
find_files( files, dirs=['root','root2'], extensions=['.mid','.txt'] )
print(files)
#['root2/dummie.txt',
# 'root/temp2/tmp.mid',
# 'root2/temp3/song.mid',
# 'root/temp1/temp1.1/f1.mid',
# 'root/temp1/temp1.2/f.mid']
【讨论】:
【参考方案4】:我会将John La Rooy's list comprehension 翻译成嵌套的for,以防其他人无法理解。
result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]
应该相当于:
import glob
import os
result = []
for x in os.walk(PATH):
for y in glob.glob(os.path.join(x[0], '*.txt')):
result.append(y)
这是list comprehension 以及函数os.walk 和glob.glob 的文档。
【讨论】:
这个答案在 Python 3.7.3 中对我有用。glob.glob(..., recursive=True)
和 list(Path(dir).glob(...'))
没有。【参考方案5】:
新的pathlib
库将其简化为一行:
from pathlib import Path
result = list(Path(PATH).glob('**/*.txt'))
你也可以使用生成器版本:
from pathlib import Path
for file in Path(PATH).glob('**/*.txt'):
pass
这将返回Path
对象,您可以将其用于几乎任何事情,或者通过file.name
将文件名作为字符串获取。
【讨论】:
【参考方案6】:递归是 Python 3.5 中的新功能,因此它不适用于 Python 2.7。这是使用 r
字符串的示例,因此您只需要在 Win、Lin、...上提供路径即可
import glob
mypath=r"C:\Users\dj\Desktop\nba"
files = glob.glob(mypath + r'\**\*.py', recursive=True)
# print(files) # as list
for f in files:
print(f) # nice looking single line per file
注意:它将列出所有文件,无论它应该有多深。
【讨论】:
【参考方案7】:此函数将递归地仅将文件放入列表中。
import os
def ls_files(dir):
files = list()
for item in os.listdir(dir):
abspath = os.path.join(dir, item)
try:
if os.path.isdir(abspath):
files = files + ls_files(abspath)
else:
files.append(abspath)
except FileNotFoundError as err:
print('invalid directory\n', 'Error: ', err)
return files
【讨论】:
【参考方案8】:您可以通过这种方式返回绝对路径文件列表。
def list_files_recursive(path):
"""
Function that receives as a parameter a directory path
:return list_: File List and Its Absolute Paths
"""
import os
files = []
# r = root, d = directories, f = files
for r, d, f in os.walk(path):
for file in f:
files.append(os.path.join(r, file))
lst = [file for file in files]
return lst
if __name__ == '__main__':
result = list_files_recursive('/tmp')
print(result)
【讨论】:
【参考方案9】:如果您不介意安装额外的灯光库,可以这样做:
pip install plazy
用法:
import plazy
txt_filter = lambda x : True if x.endswith('.txt') else False
files = plazy.list_files(root='data', filter_func=txt_filter, is_include_root=True)
结果应该是这样的:
['data/a.txt', 'data/b.txt', 'data/sub_dir/c.txt']
它适用于 Python 2.7 和 Python 3。
Github:https://github.com/kyzas/plazy#list-files
免责声明:我是plazy
的作者。
【讨论】:
【参考方案10】:这似乎是我能想到的最快的解决方案,并且比os.walk
快,并且比任何glob
解决方案都快很多。 p>
f.path
更改为f.name
来选择返回完整路径或仅返回文件名称(不要更改子文件夹!)。
参数:dir: str, ext: list
.
函数返回两个列表:subfolders, files
。
详细的速度分析见下文。
def run_fast_scandir(dir, ext): # dir: str, ext: list
subfolders, files = [], []
for f in os.scandir(dir):
if f.is_dir():
subfolders.append(f.path)
if f.is_file():
if os.path.splitext(f.name)[1].lower() in ext:
files.append(f.path)
for dir in list(subfolders):
sf, f = run_fast_scandir(dir, ext)
subfolders.extend(sf)
files.extend(f)
return subfolders, files
subfolders, files = run_fast_scandir(folder, [".jpg"])
如果您需要文件大小,您还可以创建一个sizes
列表并像这样添加f.stat().st_size
以显示 MiB:
sizes.append(f"f.stat().st_size/1024/1024:.0f MiB")
速度分析
用于获取所有子文件夹和主文件夹中具有特定文件扩展名的所有文件的各种方法。
tl;博士:
fast_scandir
显然胜出,速度是所有其他解决方案的两倍,除了 os.walk。
os.walk
排名第二,稍慢。
使用glob
会大大减慢进程。
没有一个结果使用自然排序。这意味着结果将按如下方式排序:1、10、2。要获得自然排序(1、2、10),请查看https://***.com/a/48030307/2441026
**结果:**
fast_scandir took 499 ms. Found files: 16596. Found subfolders: 439
os.walk took 589 ms. Found files: 16596
find_files took 919 ms. Found files: 16596
glob.iglob took 998 ms. Found files: 16596
glob.glob took 1002 ms. Found files: 16596
pathlib.rglob took 1041 ms. Found files: 16596
os.walk-glob took 1043 ms. Found files: 16596
测试是使用 W7x64、Python 3.8.1、20 次运行完成的。 439 个(部分嵌套)子文件夹中有 16596 个文件。find_files
来自 https://***.com/a/45646357/2441026,可让您搜索多个扩展名。fast_scandir
是我自己编写的,还会返回子文件夹列表。你可以给它一个扩展列表来搜索(我测试了一个列表,其中包含一个简单的if ... == ".jpg"
的条目,并且没有显着差异)。
# -*- coding: utf-8 -*-
# Python 3
import time
import os
from glob import glob, iglob
from pathlib import Path
directory = r"<folder>"
RUNS = 20
def run_os_walk():
a = time.time_ns()
for i in range(RUNS):
fu = [os.path.join(dp, f) for dp, dn, filenames in os.walk(directory) for f in filenames if
os.path.splitext(f)[1].lower() == '.jpg']
print(f"os.walk\t\t\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(fu)")
def run_os_walk_glob():
a = time.time_ns()
for i in range(RUNS):
fu = [y for x in os.walk(directory) for y in glob(os.path.join(x[0], '*.jpg'))]
print(f"os.walk-glob\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(fu)")
def run_glob():
a = time.time_ns()
for i in range(RUNS):
fu = glob(os.path.join(directory, '**', '*.jpg'), recursive=True)
print(f"glob.glob\t\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(fu)")
def run_iglob():
a = time.time_ns()
for i in range(RUNS):
fu = list(iglob(os.path.join(directory, '**', '*.jpg'), recursive=True))
print(f"glob.iglob\t\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(fu)")
def run_pathlib_rglob():
a = time.time_ns()
for i in range(RUNS):
fu = list(Path(directory).rglob("*.jpg"))
print(f"pathlib.rglob\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(fu)")
def find_files(files, dirs=[], extensions=[]):
# https://***.com/a/45646357/2441026
new_dirs = []
for d in dirs:
try:
new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
except OSError:
if os.path.splitext(d)[1].lower() in extensions:
files.append(d)
if new_dirs:
find_files(files, new_dirs, extensions )
else:
return
def run_fast_scandir(dir, ext): # dir: str, ext: list
# https://***.com/a/59803793/2441026
subfolders, files = [], []
for f in os.scandir(dir):
if f.is_dir():
subfolders.append(f.path)
if f.is_file():
if os.path.splitext(f.name)[1].lower() in ext:
files.append(f.path)
for dir in list(subfolders):
sf, f = run_fast_scandir(dir, ext)
subfolders.extend(sf)
files.extend(f)
return subfolders, files
if __name__ == '__main__':
run_os_walk()
run_os_walk_glob()
run_glob()
run_iglob()
run_pathlib_rglob()
a = time.time_ns()
for i in range(RUNS):
files = []
find_files(files, dirs=[directory], extensions=[".jpg"])
print(f"find_files\t\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(files)")
a = time.time_ns()
for i in range(RUNS):
subf, files = run_fast_scandir(directory, [".jpg"])
print(f"fast_scandir\ttook (time.time_ns() - a) / 1000 / 1000 / RUNS:.0f ms. Found files: len(files). Found subfolders: len(subf)")
【讨论】:
很好的解决方案,但我遇到了一个问题,我花了一点时间才弄清楚。使用您的fast_scandir
代码,当它遇到以'.'
开头且没有扩展名(例如.DS_Store
或.gitignore
)的文件路径时,if os.path.splitext(f.name)[1].lower() in ext
将始终返回true,这实际上是在询问if '' in '.jpg'
在你的例子中。我建议添加长度检查(即if len(os.path.splitext(f.name)[1]) > 0 and os.path.splitext(f.name)[1].lower() in ext
)。
@BrandonHunter,它不返回 True。 print( os.path.splitext(".DS_Store")[1].lower() in [".jpg"] )
-> False
。请记住 ext
是一个列表,而不是一个字符串。
您可以消除此函数的递归性质,方法是在函数开头将dir
附加到subfolders
,然后添加一个遍历subfolders
的外循环。这应该会带来非常小的速度提升,尤其是对于非常深的目录结构。如果您需要返回 subfolders
和 files
以外的内容,它还可以释放函数的输出。请注意,根据您添加和访问subfolders
元素的方式,输出的顺序可能会有所不同。
看起来这不是真的。在对更大数据集的代码 sn-p 进行基准测试时,它比使用 glob 的代码花费更多时间。但是代码按预期工作。【参考方案11】:
您的原始解决方案几乎是正确的,但变量“root”在递归路径时会动态更新。 os.walk() 是一个递归生成器。每个(根、子文件夹、文件)元组集都是针对特定根的设置方式。
即
root = 'C:\\'
subFolder = ['Users', 'ProgramFiles', 'ProgramFiles (x86)', 'Windows', ...]
files = ['foo1.txt', 'foo2.txt', 'foo3.txt', ...]
root = 'C:\\Users\\'
subFolder = ['UserAccount1', 'UserAccount2', ...]
files = ['bar1.txt', 'bar2.txt', 'bar3.txt', ...]
...
我对您的代码稍作调整以打印完整列表。
import os
for root, subFolder, files in os.walk(PATH):
for item in files:
if item.endswith(".txt") :
fileNamePath = str(os.path.join(root,item))
print(fileNamePath)
希望这会有所帮助!
编辑:(基于反馈)
OP 误解/错误标记了 subFolder 变量,因为它实际上是 “根”中的所有子文件夹。因此,OP,您正在尝试执行 os.path.join(str, list, str),这可能不会像您预期的那样工作。
为了帮助增加清晰度,您可以尝试以下标签方案:
import os
for current_dir_path, current_subdirs, current_files in os.walk(RECURSIVE_ROOT):
for aFile in current_files:
if aFile.endswith(".txt") :
txt_file_path = str(os.path.join(current_dir_path, aFile))
print(txt_file_path)
【讨论】:
优雅的解决方案 - 感谢您解释 walk 的递归生成器! 从某种意义上说,这应该是公认的答案,尽管我觉得它也许可以更详细地解释 OP 的错误。 @triplee:添加了详细信息。感谢您的反馈。 :)【参考方案12】:您可以使用 glob 模块中的“递归”设置来搜索子目录
例如:
import glob
glob.glob('//Mypath/folder/**/*',recursive = True)
第二行将返回该文件夹位置的子目录中的所有文件(注意,您需要在文件夹字符串末尾添加“**/*”字符串才能执行此操作。)
如果你特别想在你的子目录深处找到文本文件,你可以使用
glob.glob('//Mypath/folder/**/*.txt',recursive = True)
【讨论】:
【参考方案13】:最简单最基本的方法:
import os
for parent_path, _, filenames in os.walk('.'):
for f in filenames:
print(os.path.join(parent_path, f))
【讨论】:
以上是关于如何进行递归子文件夹搜索并返回列表中的文件?的主要内容,如果未能解决你的问题,请参考以下文章