Использовать 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') : так же, как 1
  • glob.glob('**/*.c') : соответствует всем файлам, заканчивающимся на .c только в ближайших подкаталогах, но не в текущем каталоге
  • glob.glob('*.c',recursive=True) : то же, что и 1
  • glob.glob('*/*.c',recursive=True) : то же, что и 3
  • glob.glob('**/*.c',recursive=True) : соответствует всем файлам, заканчивающимся на .c в текущем каталоге и во всех подкаталогах

Рассматривать pathlib.rglob(),

Это как звонить Path.glob() с "**/" добавлено перед данным относительным шаблоном:

import pathlib


for p in pathlib.Path("src").rglob("*.c"):
    print(p)

Смотрите также связанный пост @taleinat здесь и более ранний пост в другом месте.

Если это может кого-то заинтересовать, я описал три основных предложенных метода. У меня есть около 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 не найдено ни одного подходящего файла.

Другие вопросы по тегам