Как использовать glob () для рекурсивного поиска файлов?

Вот что у меня есть:

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'))

Но это явно ограниченно и неуклюже.


person Ben Gartner    schedule 02.02.2010    source источник


Ответы (27)


pathlib.Path.rglob

Используйте pathlib.Path.rglob из _ 2_ модуль, который был представлен в Python 3.5.

from pathlib import Path

for path in Path('src').rglob('*.c'):
    print(path.name)

Если вы не хотите использовать pathlib, используйте _4 _, но не забудьте передать параметр ключевого слова recursive, и он будет использовать чрезмерное количество времени для больших каталогов.

Для случаев, когда совпадающие файлы начинаются с точки (.); например файлы в текущем каталоге или скрытые файлы в системе на основе Unix, используйте os.walk решение ниже.

os.walk

Для более старых версий Python используйте 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))
person Johan Dahlin    schedule 02.02.2010
comment
Для Python старше 2.2 существует os.path.walk(), который немного сложнее использовать, чем os.walk() - person John La Rooy; 02.02.2010
comment
@gnibbler Я знаю, что это старый комментарий, но мой комментарий предназначен только для того, чтобы люди знали, что os.path.walk() устарел и был удален в Python 3. - person Pedro Cunha; 18.01.2013
comment
почему бы не .endwith ('. c'), я думаю, в этом сценарии это будет быстрее, чем fnmatch? - person DevC; 18.03.2014
comment
@DevC, который может работать в конкретном случае, заданном в этом вопросе, но легко представить кого-то, кто захочет использовать его с такими запросами, как 'a * .c' и т. Д., Поэтому я думаю, что стоит оставить текущий несколько медленный ответ. - person Johan Dahlin; 19.05.2014
comment
Я согласен с тем, что это золотой стандарт. По какой-то причине мне не удалось импортировать glob как root. В приглашении обычного пользователя он работал, но не в приглашении root, fedora 20, python 2.7. Итак, fnmatch и этот ответ - подарок. - person DarkForce; 17.03.2015
comment
Для решения Python 3 glob пропускает каталоги, начинающиеся с ., если вы специально не кодируете их. Я думаю, что обход дерева каталогов с использованием os.walk - более надежное и, в конечном счете, более простое решение. - person aparkerlue; 10.11.2017
comment
Как бы то ни было, в моем случае поиск более 10 000 файлов с помощью glob был намного медленнее, чем с os.walk, поэтому по этой причине я выбрал последнее решение. - person Godsmith; 12.09.2018
comment
Для python 3.4 pathlib.Path('src').glob('**/*.c') должен работать. - person CivFan; 11.04.2019
comment
Как я могу исключить файлы и / или подпапки из этого пути? - person Sebas Pinto; 24.10.2019
comment
@JohanDahlin Я заметил, что вы обновили свой исходный пост (предлагая os.walk и glob.glob) уже опубликованными ответами: fnmatch.filter (@Alex Martelli), glob.glob (@ chris-piekarski), pathlib.Path.glob (@taleinat), а теперь и pathlib.Path.rglob. Я предполагаю, что это непреднамеренно. Могу я порекомендовать как можно лучше цитировать сообщения, предшествующие вашим обновлениям? В противном случае хорошая работа других, которая послужила причиной вашего обновления, может быть упущена из виду. Спасибо, что регулярно обновляете свои сообщения. - person pylang; 24.10.2019
comment
Это также будет соответствовать каталогам, заканчивающимся на .c. Если вам действительно нужны только файлы, вы должны проверить это. - person qwr; 28.07.2020

Подобно другим решениям, но с использованием 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

Кроме того, использование генератора позволяет обрабатывать каждый найденный файл вместо того, чтобы находить все файлы и затем обрабатывать их.

person Bruno Oliveira    schedule 02.02.2010

Я модифицировал модуль glob для поддержки ** рекурсивного подстановки, например:

>>> import glob2
>>> all_header_files = glob2.glob('src/**/*.c')

https://github.com/miracle2k/python-glob2/

Полезно, когда вы хотите предоставить своим пользователям возможность использовать синтаксис **, и поэтому одной os.walk () недостаточно.

person miracle2k    schedule 26.06.2011
comment
Можем ли мы остановить это после того, как он найдет первое совпадение? Может быть, можно будет использовать его как генератор вместо того, чтобы он возвращал список всех возможных результатов? Кроме того, это DFS или BFS? Я думаю, что я бы предпочел BFS, чтобы файлы, которые находятся рядом с корнем, были найдены первыми. +1 за создание этого модуля и его размещение на GitHub / pip. - person ArtOfWarfare; 05.08.2014
comment
Синтаксис ** был добавлен в официальный модуль glob в Python 3.5. - person ArtOfWarfare; 26.01.2015
comment
@ArtOfWarfare Хорошо, хорошо. Это все еще полезно для ‹3.5. - person cs95; 03.07.2017
comment
Чтобы активировать рекурсивную подстановку с помощью ** с официальным модулем glob, выполните: glob(path, recursive=True) - person winklerrr; 29.03.2020

Начиная с 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().

person taleinat    schedule 11.11.2014
comment
Действительно, и он будет в Python 3.5. Предполагалось, что это уже будет так в Python 3.4, но было по ошибке пропущено. - person taleinat; 24.02.2015
comment
Этот синтаксис теперь поддерживается glob.glob () начиная с Python 3.5. - person taleinat; 04.08.2015
comment
Обратите внимание, что вы также можете использовать pathlib.PurePath.relative_to в комбинации, чтобы получить относительные пути. Дополнительную информацию см. В моем ответе здесь. - person pjgranahan; 25.04.2017

Для python ›= 3.5 вы можете использовать **, recursive=True:

import glob
for f in glob.glob('/path/**/*.c', recursive=True):
    print(f)

Демо


Если рекурсивный True, шаблон ** будет соответствовать любым файлам и нулю или более directories и subdirectories. Если за шаблоном следует os.sep, совпадают только каталоги и subdirectories.


Примечание:

Python3.6, кажется, по умолчанию recursive=True при использовании **, поэтому его можно не указывать.

Демо Python 3.6

person Pedro Lobito    schedule 25.08.2019
comment
Это работает лучше, чем pathlib.Path ('./ path /'). Glob ('* /'), потому что это также так в папке с размером 0 - person Charles Walker; 18.04.2020
comment
В Python 3.9.1 для рекурсии по умолчанию установлено значение False. - person PYB; 31.12.2020

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 дает вам те же шаблоны, что и _ 3_, так что это действительно отличная замена glob.glob с очень близкими семантика. Итеративная версия (например, генератор), IOW вместо glob.iglob, представляет собой тривиальную адаптацию (просто yield промежуточные результаты по ходу, вместо extend возврата одного списка результатов в конце).

person Alex Martelli    schedule 02.02.2010
comment
Что вы думаете об использовании recursive_glob(pattern, treeroot='.'), как я предложил в своей редакции? Таким образом, он может называться, например, recursive_glob('*.txt') и интуитивно соответствовать синтаксису glob. - person Chris Redford; 05.01.2015
comment
@ChrisRedford, в любом случае я считаю это довольно незначительной проблемой. В его нынешнем виде он сопоставляет файлы, затем порядок аргументов шаблона fnmatch.filter, что примерно так же полезно, как возможность сопоставления с одним аргументом glob.glob. - person Alex Martelli; 05.01.2015

Вы захотите использовать 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))
person Geoff Reedy    schedule 02.02.2010

Вот решение с вложенными списками, 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')
person akaihola    schedule 02.11.2011

Рассмотрим pathlib.rglob().

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

import pathlib


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

См. Также сообщение, связанное с @aleinat, и аналогичную опубликовать в другом месте.

person pylang    schedule 23.05.2019

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, в текущем каталоге и во всех подкаталогах
person Milovan Tomašević    schedule 03.08.2020

Недавно мне пришлось восстанавливать свои картинки с расширением .jpg. Я запустил photorec и восстановил 4579 каталогов, в которых содержится 2,2 миллиона файлов с огромным разнообразием расширений. С помощью приведенного ниже скрипта я смог выбрать 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)
person Mustafa Çetin    schedule 05.01.2013

на основе других ответов это моя текущая рабочая реализация, которая извлекает вложенные файлы xml в корневом каталоге:

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

Мне очень нравится питон :)

person daveoncode    schedule 28.07.2012

Йохан и Бруно предлагают отличные решения по минимальным требованиям, как указано. Я только что выпустил Formic, который реализует Ant FileSet и Globs, которые могут обрабатывать этот и более сложные сценарии. Реализация вашего требования:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
person Andrew Alcock    schedule 15.05.2012
comment
Муравьин, кажется, заброшен ?! И он не поддерживает Python 3 (bitbucket.org/aviser/formic/ issue / 12 / support-python-3) - person blueyed; 04.09.2014

Для 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'
person Sami    schedule 21.06.2019
comment
Ваша первая строка кода не работает для просмотра подкаталогов. Но если вы просто расширите его на /**, у меня это сработает, например: file_names_array = glob.glob('src/**/*.c', recursive=True) - person NeStack; 28.08.2019

Другой способ сделать это, используя только модуль 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
person chris-piekarski    schedule 13.09.2011

Или с пониманием списка:

 >>> 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") ] 
person xtofl    schedule 24.06.2013

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

person Daniel    schedule 13.06.2020

Только что сделал это .. он будет распечатывать файлы и каталог в иерархическом порядке.

Но я не использовал fnmatch или прогулку

#!/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)
person Shaurya Gupta    schedule 27.07.2013

Этот использует 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')])
person hipertracker    schedule 02.08.2013

В дополнение к предлагаемым ответам вы можете сделать это с помощью некоторой ленивой генерации и магии понимания списка:

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, имеющие эту структуру.

person f0xdx    schedule 05.12.2015

Это рабочий код на 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)

Надеюсь, это поможет кому-то бороться с подобными проблемами.

person Sanjay Bharwani    schedule 03.04.2020

Упрощенная версия ответа Йохана Далина без 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']
person flowfree    schedule 03.06.2013

Вот мое решение, использующее понимание списка для поиска нескольких расширений файлов рекурсивно в каталоге и во всех подкаталогах:

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
person sackpower    schedule 18.08.2014

Если файлы находятся в удаленной файловой системе или внутри архива, вы можете использовать реализацию fsspec AbstractFileSystem class. Например, чтобы перечислить все файлы в 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.

person gerrit    schedule 08.10.2020

Я изменил верхний ответ в этом сообщении ... и недавно создал этот скрипт, который будет перебирать все файлы в заданном каталоге (searchdir) и подкаталогах под ним ... и печатает имя файла, корневой каталог, дату изменения / создания и размер.

Надеюсь, это кому-то поможет ... и они смогут пройти по каталогу и получить информацию о файлах.

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))
person ihightower    schedule 15.11.2014

Вот решение, которое сопоставит шаблон с полным путем, а не только с базовым именем файла.

Он использует fnmatch.translate для преобразования глобуса -style в регулярное выражение, которое затем сопоставляется с полным путем к каждому файлу, найденному при просмотре каталога.

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
person yoyo    schedule 30.06.2015

Мне нужно было решение для python 2.x, которое быстро работает с большими каталогами.
Я остановился на этом:

import subprocess
foundfiles= subprocess.check_output("ls src/*.c src/**/*.c", shell=True)
for foundfile in foundfiles.splitlines():
    print foundfile

Обратите внимание, что вам может потребоваться некоторая обработка исключений, если ls не найдет ни одного подходящего файла.

person Roman    schedule 23.06.2017
comment
Я только что понял, что ls src/**/*.c работает, только если включена опция globstar (shopt -s globstar) - подробности см. В этом ответе. - person Roman; 27.06.2017
comment
Подпроцесс никогда не является хорошим решением, если вы хотите работать быстро, а ls в сценариях определенно следует избегать. - person tripleee; 18.12.2020
comment
Хорошо, я не знал об этом. У меня это работает - и занимает меньше секунды (вместо более 30 секунд ...) - person Roman; 19.12.2020