Як використовувати 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, який може працювати в конкретному випадку, заданому в цьому запитанні, але легко уявити, хтось, хто хоче це використовувати, використовує такі запити, як 'a * .c' і т.д.
Йохан Далін

2
Для чого це варто, в моєму випадку пошук 10 000+ файлів з глобусом був набагато повільнішим, ніж з os.walk, тому я пішов із останнім рішенням з цієї причини.
Godsmith

2
Для python 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 () недостатньо хороший.


2
Чи можемо ми зробити цю зупинку після того, як вона знайде перший матч? Може бути можливим використовувати його як генератор, а не повертати йому список усіх можливих результатів? Також це DFS чи BFS? Думаю, я вважаю за краще BFS, щоб файли, що знаходяться біля кореня, були знайдені першими. +1 для створення цього модуля та надання його в GitHub / pip.
ArtOfWarfare

14
Синтаксис ** був доданий до офіційного глобального модуля в 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().


3
Дійсно, і це буде в Python 3.5 . У Python 3.4 це повинно було бути таким, але помилково було пропущено .
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, я вважаю це досить незначним питанням. Як і зараз, він відповідає порядку аргументів "файли потім шаблон" 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збігаються лише каталоги .


2
Це працює краще ніж pathlib.Path ('./ path /'). Glob (' * / '), тому що це також так у папці розміром 0
Charles Walker

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

7

Нещодавно мені довелося відновити свої фотографії з розширенням .jpg. Я запустив фоторекламу і відновив 4579 каталогів 2,2 мільйона файлів в межах, маючи величезну різноманітність розширень. За сценарієм нижче я зміг вибрати 50133 файлів.

#!/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 тут і подібний пост у інших місцях.


5

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

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name

1
Форма, здається, покинута ?! І він не підтримує 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"))

Я дуже розважаюся з python :)


3

Ще один спосіб зробити це, використовуючи лише глобальний модуль. Просто надішліть метод 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)
NeStack

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") ] 

2

Щойно зробив це .. він буде друкувати файли та каталоги ієрархічно

Але я не використовував 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)

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

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, які мають цю структуру.


2

Це робочий код на Python 2.7. Як частина моєї роботи девепса, мені потрібно було написати сценарій, який перемістить файли конфігурації, позначені 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']

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

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)

0

Я змінив верхню відповідь у цій публікації .. і нещодавно створив цей скрипт, який буде перебирати всі файли в заданій директорії (searchdir) та підкаталогами під нею ... та друкує ім'я файлу, rootdir, модифіковану дату створення та розмір.

Сподіваюся, це допоможе комусь ... і вони можуть перейти до каталогу та отримати інформацію про файли.

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

0

Ось рішення, яке відповідатиме шаблону проти повного шляху, а не лише базового імені файлу.

Він використовує fnmatch.translateдля перетворення шаблону в глобальному стилі в звичайний вираз, який потім узгоджується з повним шляхом кожного файлу, знайденого під час проходження каталогу.

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) - детальну інформацію див. У цій відповіді .
Роман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.