Использовать Glob() для рекурсивного поиска файлов в Python?
Вот что у меня есть:
glob(os.path.join('src','*.c'))
но я хочу найти подпапки src. Примерно так будет работать:
glob(os.path.join('src','*.c'))
glob(os.path.join('src','*','*.c'))
glob(os.path.join('src','*','*','*.c'))
glob(os.path.join('src','*','*','*','*.c'))
Но это явно ограничено и неуклюже.
21 ответ
Python 3.5+
Начиная с Python версии 3.5, glob
модуль поддерживает "**"
директива (которая анализируется только если вы передаете recursive
флаг):
import glob
for filename in glob.iglob('src/**/*.c', recursive=True):
print(filename)
Если вам нужен список, просто используйте glob.glob
вместо glob.iglob
,
Для случаев, когда совпадающие файлы начинаются с точки (.); как файлы в текущем каталоге или скрытые файлы в системе на базе Unix, используйте os.walk
Решение ниже.
Python 2.2 до 3.4
Для более старых версий Python, начиная с Python 2.2, используйте os.walk
рекурсивно ходить по каталогу и fnmatch.filter
сопоставить с простым выражением:
import fnmatch
import os
matches = []
for root, dirnames, filenames in os.walk('src'):
for filename in fnmatch.filter(filenames, '*.c'):
matches.append(os.path.join(root, filename))
Python 2.1 и более ранние
Для более старых версий Python используйте glob.glob
против каждого имени файла вместо fnmatch.filter
,
Для python> = 3.5 вы можете использовать **
, recursive=True
:
import glob
for x in glob.glob('path/**/*.c', recursive=True):
print(x)
Если рекурсивно верно, шаблон
**
будет соответствовать любым файлам и ноль или болееdirectories
а такжеsubdirectories
, Если шаблон сопровождаетсяos.sep
, только каталоги иsubdirectories
матч.
Аналогично другим решениям, но с использованием fnmatch.fnmatch вместо glob, так как os.walk уже перечислил имена файлов:
import os, fnmatch
def find_files(directory, pattern):
for root, dirs, files in os.walk(directory):
for basename in files:
if fnmatch.fnmatch(basename, pattern):
filename = os.path.join(root, basename)
yield filename
for filename in find_files('src', '*.c'):
print 'Found C source:', filename
Кроме того, использование генератора позволяет обрабатывать каждый файл так, как он был найден, вместо того, чтобы находить все файлы и затем обрабатывать их.
Я изменил модуль glob для поддержки ** для рекурсивного сглаживания, например:
>>> import glob2
>>> all_header_files = glob2.glob('src/**/*.c')
https://github.com/miracle2k/python-glob2/
Полезно, когда вы хотите предоставить своим пользователям возможность использовать синтаксис **, и, таким образом, одного os.walk() недостаточно.
Начиная с Python 3.4, можно использовать glob()
метод одного из Path
классы в новом модуле pathlib, который поддерживает **
подстановочные знаки. Например:
from pathlib import Path
for file_path in Path('src').glob('**/*.c'):
print(file_path) # do whatever you need with these files
Обновление: Начиная с Python 3.5, тот же синтаксис также поддерживается glob.glob()
,
import os
import fnmatch
def recursive_glob(treeroot, pattern):
results = []
for base, dirs, files in os.walk(treeroot):
goodfiles = fnmatch.filter(files, pattern)
results.extend(os.path.join(base, f) for f in goodfiles)
return results
fnmatch
дает вам точно такие же шаблоны, как glob
так что это действительно отличная замена glob.glob
с очень близкой семантикой. Итерационная версия (например, генератор), IOW замена для glob.iglob
Обыкновенная адаптация (всего yield
промежуточные результаты, как вы идете, а не extend
единый список результатов, чтобы вернуться в конце).
Вы хотите использовать os.walk
собирать имена файлов, которые соответствуют вашим критериям. Например:
import os
cfiles = []
for root, dirs, files in os.walk('src'):
for file in files:
if file.endswith('.c'):
cfiles.append(os.path.join(root, file))
Вот решение с использованием вложенных списков, os.walk
и простое сопоставление суффиксов вместо glob
:
import os
cfiles = [os.path.join(root, filename)
for root, dirnames, filenames in os.walk('src')
for filename in filenames if filename.endswith('.c')]
Он может быть сжат до одной строки:
import os;cfiles=[os.path.join(r,f) for r,d,fs in os.walk('src') for f in fs if f.endswith('.c')]
или обобщенный как функция:
import os
def recursive_glob(rootdir='.', suffix=''):
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames if filename.endswith(suffix)]
cfiles = recursive_glob('src', '.c')
Если вам нужно полное glob
шаблоны стилей, вы можете следовать примеру Алекса и Бруно и использовать fnmatch
:
import fnmatch
import os
def recursive_glob(rootdir='.', pattern='*'):
return [os.path.join(looproot, filename)
for looproot, _, filenames in os.walk(rootdir)
for filename in filenames
if fnmatch.fnmatch(filename, pattern)]
cfiles = recursive_glob('src', '*.c')
import os, glob
for each in glob.glob('path/**/*.c', recursive=True):
print(f'Name with path: {each} \nName without path: {os.path.basename(each)}')
glob.glob('*.c')
: соответствует всем файлам, заканчивающимся на.c
в текущем каталогеglob.glob('*/*.c')
: так же, как 1glob.glob('**/*.c')
: соответствует всем файлам, заканчивающимся на.c
только в ближайших подкаталогах, но не в текущем каталогеglob.glob('*.c',recursive=True)
: то же, что и 1glob.glob('*/*.c',recursive=True)
: то же, что и 3glob.glob('**/*.c',recursive=True)
: соответствует всем файлам, заканчивающимся на.c
в текущем каталоге и во всех подкаталогах
Если это может кого-то заинтересовать, я описал три основных предложенных метода. У меня есть около 500 КБ файлов в общей папке и 2 КБ файлов, соответствующих желаемому шаблону.
вот (очень простой) код
import glob
import json
import fnmatch
import os
from pathlib import Path
from time import time
def find_files_iglob():
return glob.iglob("./data/**/reg_data.json", recursive=True)
def find_files_oswalk():
for root, dirnames, filenames in os.walk('data'):
for filename in fnmatch.filter(filenames, 'reg_data.json'):
yield os.path.join(root, filename)
def find_files_rglob():
return Path('data').rglob('reg_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.6sec
rglob ~14.5sec
iglob: ~16.9sec
Платформа: Ubuntu 16.04, x86_64 (ядро i7),
Недавно мне пришлось восстанавливать свои фотографии с расширением.jpg. Я запустил PhotoRec и восстановил 4579 каталогов с 2,2 миллионами файлов, имеющих огромное разнообразие расширений. С помощью приведенного ниже скрипта я смог выбрать 50133 файлов с расширением havin .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)
Основываясь на других ответах, это моя текущая рабочая реализация, которая извлекает вложенные файлы XML в корневой каталог:
files = []
for root, dirnames, filenames in os.walk(myDir):
files.extend(glob.glob(root + "/*.xml"))
Мне правда весело с питоном:)
Для Python 3.5 и выше
file_names_array = glob.glob('src/*.c', recursive=True)
Изменить: как @NeStack руководствуясь, если выше не работает для вас, пожалуйста, попробуйте
file_names_array = glob.glob('src/**.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'
Йохан и Бруно предлагают отличные решения по минимальным требованиям, как указано. Я только что выпустил Formic, который реализует Ant FileSet и Globs, которые могут справиться с этим и более сложными сценариями. Реализация вашего требования:
import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
print file_name
Еще один способ сделать это, используя только модуль 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
Если файлы находятся в удаленной файловой системе или внутри архива, вы можете использовать реализацию класса 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. Дополнительные сведения см. В документации API fsspec.
Или с пониманием списка:
>>> 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") ]
Только что сделал это.. он будет печатать файлы и каталог в иерархическом порядке
Но я не использовал fnmatch или walk
#!/usr/bin/python
import os,glob,sys
def dirlist(path, c = 1):
for i in glob.glob(os.path.join(path, "*")):
if os.path.isfile(i):
filepath, filename = os.path.split(i)
print '----' *c + filename
elif os.path.isdir(i):
dirname = os.path.basename(i)
print '----' *c + dirname
c+=1
dirlist(i,c)
c-=1
path = os.path.normpath(sys.argv[1])
print(os.path.basename(path))
dirlist(path)
В дополнение к предлагаемым ответам, вы можете сделать это с помощью некоторого ленивого поколения и магии понимания списка:
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')
чтобы получить все.c файлы во всех подкаталогах src, которые имеют эту структуру.
Тот использует fnmatch или регулярное выражение:
import fnmatch, os
def filepaths(directory, pattern):
for root, dirs, files in os.walk(directory):
for basename in files:
try:
matched = pattern.match(basename)
except AttributeError:
matched = fnmatch.fnmatch(basename, pattern)
if matched:
yield os.path.join(root, basename)
# usage
if __name__ == '__main__':
from pprint import pprint as pp
import re
path = r'/Users/hipertracker/app/myapp'
pp([x for x in filepaths(path, re.compile(r'.*\.py$'))])
pp([x for x in filepaths(path, '*.py')])
Это рабочий код на 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)
Надеюсь, это поможет кому-то бороться с подобными проблемами.
Вот мое решение, использующее списки для рекурсивного поиска нескольких расширений файлов в каталоге и во всех подкаталогах:
import os, glob
def _globrec(path, *exts):
""" Glob recursively a directory and all subdirectories for multiple file extensions
Note: Glob is case-insensitive, i. e. for '\*.jpg' you will get files ending
with .jpg and .JPG
Parameters
----------
path : str
A directory name
exts : tuple
File extensions to glob for
Returns
-------
files : list
list of files matching extensions in exts in path and subfolders
"""
dirs = [a[0] for a in os.walk(path)]
f_filter = [d+e for d in dirs for e in exts]
return [f for files in [glob.iglob(files) for files in f_filter] for f in files]
my_pictures = _globrec(r'C:\Temp', '\*.jpg','\*.bmp','\*.png','\*.gif')
for f in my_pictures:
print f
Упрощенная версия ответа Йохана Далина, без fnmatch.
import os
matches = []
for root, dirnames, filenames in os.walk('src'):
matches += [os.path.join(root, f) for f in filenames if f[-2:] == '.c']
Я изменил верхний ответ в этой публикации... и недавно создал этот скрипт, который будет перебирать все файлы в данном каталоге (searchdir) и подкаталогах в нем... и печатать имя файла, rootdir, дату изменения / создания и размер.
Надеюсь, это кому-нибудь поможет... и они могут пройтись по каталогу и получить fileinfo.
import time
import fnmatch
import os
def fileinfo(file):
filename = os.path.basename(file)
rootdir = os.path.dirname(file)
lastmod = time.ctime(os.path.getmtime(file))
creation = time.ctime(os.path.getctime(file))
filesize = os.path.getsize(file)
print "%s**\t%s\t%s\t%s\t%s" % (rootdir, filename, lastmod, creation, filesize)
searchdir = r'D:\Your\Directory\Root'
matches = []
for root, dirnames, filenames in os.walk(searchdir):
## for filename in fnmatch.filter(filenames, '*.c'):
for filename in filenames:
## matches.append(os.path.join(root, filename))
##print matches
fileinfo(os.path.join(root, filename))
Вот решение, которое сопоставит шаблон с полным путем, а не только с базовым именем файла.
Оно использует fnmatch.translate
преобразовать шаблон в стиле glob в регулярное выражение, которое затем сопоставляется с полным путем каждого файла, найденного при обходе каталога.
re.IGNORECASE
необязателен, но желателен в Windows, поскольку сама файловая система не чувствительна к регистру. (Я не удосужился скомпилировать регулярное выражение, потому что в документах указано, что его следует кэшировать внутри.)
import fnmatch
import os
import re
def findfiles(dir, pattern):
patternregex = fnmatch.translate(pattern)
for root, dirs, files in os.walk(dir):
for basename in files:
filename = os.path.join(root, basename)
if re.search(patternregex, filename, re.IGNORECASE):
yield filename
import sys, os, glob
dir_list = ["c:\\books\\heap"]
while len(dir_list) > 0:
cur_dir = dir_list[0]
del dir_list[0]
list_of_files = glob.glob(cur_dir+'\\*')
for book in list_of_files:
if os.path.isfile(book):
print(book)
else:
dir_list.append(book)
Мне нужно решение для Python 2.x, которое работает быстро на больших каталогах.
Я согласен с этим:
import subprocess
foundfiles= subprocess.check_output("ls src/*.c src/**/*.c", shell=True)
for foundfile in foundfiles.splitlines():
print foundfile
Обратите внимание, что вам может потребоваться некоторая обработка исключений в случае ls
не найдено ни одного подходящего файла.