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

738

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

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

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

Бен Гартнер
источник

Ответы:

1355

Python 3.5+

Поскольку вы находитесь на новом питоне, вы должны использовать pathlib.Path.rglobиз pathlibмодуля.

from pathlib import Path

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

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

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

Старые версии Python

Для более старых версий 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))
Йохан Далин
источник
3
Для Python старше 2.2 есть os.path.walk()что-то более удобное в использовании, чемos.walk()
John La Rooy
20
@gnibbler Я знаю, что это старый комментарий, но мой комментарий просто для того, чтобы люди знали, что os.path.walk()он устарел и был удален в Python 3.
Педро Кунья
5
@DevC, который может работать в конкретном случае, задаваемом в этом вопросе, но легко представить, что кто-то захочет использовать его с такими запросами, как «* .c» и т. Д., Поэтому я думаю, что текущий ответ стоит оставить несколько медленным.
Йохан Далин
2
Что бы это ни стоило, в моем случае поиск более 10000 файлов с glob был намного медленнее, чем с os.walk, поэтому я выбрал последнее решение по этой причине.
Кузнец
2
Для питона 3.4, pathlib.Path('src').glob('**/*.c')должно работать.
CivFan
111

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

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

Бруно Оливейра
источник
3
потому что 1-вкладыши - это весело:reduce(lambda x, y: x+y, map(lambda (r,_,x):map(lambda f: r+'/'+f, filter(lambda f: fnmatch.fnmatch(f, pattern), x)), os.walk('src/webapp/test_scripts')))
njzk2
1
@ njzk2(os.path.join(root,filename) for root, dirs, files in os.walk(directory) for filename in files if fnmatch.fnmatch(filename, pattern))
Baldrickk
73

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

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

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

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

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

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

taleinat
источник
3
Действительно, так и будет в Python 3.5 . Предполагалось, что так будет и в Python 3.4, но по ошибке было опущено .
taleinat
Этот синтаксис теперь поддерживается glob.glob () начиная с Python 3.5 .
taleinat
Обратите внимание, что вы также можете использовать pathlib.PurePath.relative_to в комбинации, чтобы получить относительные пути. Смотрите мой ответ здесь для большего контекста.
pjgranahan
40
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создавать единый список результатов для возврата в конце).

Алекс Мартелли
источник
1
Что вы думаете об использовании, recursive_glob(pattern, treeroot='.')как я предложил в моем редактировании? Таким образом, его можно вызвать, например, как recursive_glob('*.txt')и интуитивно соответствовать синтаксису glob.
Крис Редфорд
@ChrisRedford, я вижу это как незначительную проблему в любом случае. В нынешнем виде он соответствует порядку аргументов «files then pattern» fnmatch.filter, что примерно так же полезно, как и возможность сопоставления с одним аргументом glob.glob.
Алекс Мартелли
25

Для питона> = 3,5 вы можете использовать **, recursive=True:

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

демонстрация


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

CONvid19
источник
2
Это работает лучше, чем pathlib.Path ('./ path /'). Glob (' * / '), потому что это также так в папке с размером 0
Чарльз Уокер
20

Вы хотите использовать 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))
Джефф Риди
источник
15

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

Недавно мне пришлось восстанавливать свои фотографии с расширением .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)
Мустафа Четин
источник
7

Посмотрим pathlib.rglob().

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

import pathlib


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

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

pylang
источник
5

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

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Эндрю Алкок
источник
1
Formic, кажется, заброшен ?! И он не поддерживает Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
голубоватый
5

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

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

Мне правда весело с питоном :)

daveoncode
источник
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

Для 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'
Сами
источник
3
Ваша первая строка кода не работает для просмотра подкаталогов. Но если вы просто расширите его, /**это file_names_array = glob.glob('src/**/*.c', recursive=True)
сработает
2

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

 >>> 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") ] 
xtofl
источник
2

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

Но я не использовал 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)
Шаурья Гупта
источник
2

Тот использует 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')])
hipertracker
источник
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')для получения всех файлов .c во всех подкаталоги src, которые имеют эту структуру.

f0xdx
источник
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)

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

Санджай Бхарвани
источник
1

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

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

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
sackpower
источник
0
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)
serega386
источник
0

Я изменил верхний ответ в этой публикации ... и недавно создал этот скрипт, который будет перебирать все файлы в данном каталоге (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))
ihightower
источник
0

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

Оно использует 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
Йоу йоу
источник
0

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

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

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

Роман
источник
Я только что понял, что ls src/**/*.cработает , только если включена опция globstar ( shopt -s globstar) - подробности смотрите в этом ответе .
Роман