Как я могу искать подпапки с помощью модуля glob.glob в Python?

Я хочу открыть ряд подпапок в папке, найти несколько текстовых файлов и распечатать несколько строк текстовых файлов. Я использую это:

configfiles = glob.glob('C:/Users/sam/Desktop/file1/*.txt')

Но это также не может получить доступ к подпапкам. Кто-нибудь знает, как я могу использовать ту же команду для доступа к подпапкам, а?

9 ответов

Решение

В Python 3.5 и новее используется новый рекурсив **/ функциональность:

configfiles = glob.glob('C:/Users/sam/Desktop/file1/**/*.txt', recursive=True)

когда recursive установлено, ** с последующим разделителем пути соответствует 0 или более подкаталогам.

В более ранних версиях Python, glob.glob() не может перечислить файлы в подкаталогах рекурсивно.

В этом случае я бы использовал os.walk() в сочетании с fnmatch.filter() вместо:

import os
import fnmatch

path = 'C:/Users/sam/Desktop/file1'

configfiles = [os.path.join(dirpath, f)
    for dirpath, dirnames, files in os.walk(path)
    for f in fnmatch.filter(files, '*.txt')]

Это рекурсивно проведет ваши каталоги и вернет все абсолютные пути к соответствующим .txt файлы. В этом конкретном случае fnmatch.filter() может быть излишним, вы также можете использовать .endswith() тестовое задание:

import os

path = 'C:/Users/sam/Desktop/file1'

configfiles = [os.path.join(dirpath, f)
    for dirpath, dirnames, files in os.walk(path)
    for f in files if f.endswith('.txt')]

По этой теме много путаницы. Дайте мне посмотреть, смогу ли я это прояснить (Python 3.7):

  1. glob.glob('*.txt') :соответствует всем файлам, заканчивающимся на '.txt' в текущем каталоге
  2. glob.glob('*/*.txt') :как 1
  3. glob.glob('**/*.txt') :соответствует всем файлам, оканчивающимся на '.txt', только в непосредственных подкаталогах, но не в текущем каталоге
  4. glob.glob('*.txt',recursive=True) :как 1
  5. glob.glob('*/*.txt',recursive=True) :как 3
  6. glob.glob('**/*.txt',recursive=True):соответствует всем файлам, заканчивающимся на '.txt', в текущем каталоге и во всех подкаталогах

Так что лучше всегда указывать recursive=True.

Чтобы найти файлы в непосредственных подкаталогах:

configfiles = glob.glob(r'C:\Users\sam\Desktop\*\*.txt')

Для рекурсивной версии, которая пересекает все подкаталоги, вы можете использовать ** и передать recursive=True начиная с Python 3.5:

configfiles = glob.glob(r'C:\Users\sam\Desktop\**\*.txt', recursive=True)

Оба вызова функций возвращают списки. Вы могли бы использовать glob.iglob() возвращать пути один за другим. Или использоватьpathlib:

from pathlib import Path

path = Path(r'C:\Users\sam\Desktop')
txt_files_only_subdirs = path.glob('*/*.txt')
txt_files_all_recursively = path.rglob('*.txt') # including the current dir

Оба метода возвращают итераторы (вы можете получить пути один за другим).

Пакет glob2 поддерживает подстановочные знаки и достаточно быстр

code = '''
import glob2
glob2.glob("files/*/**")
'''
timeit.timeit(code, number=1)

На моем ноутбуке требуется примерно 2 секунды, чтобы сопоставить >60000 путей к файлам.

Вы можете использовать Formic с Python 2.6

import formic
fileset = formic.FileSet(include="**/*.txt", directory="C:/Users/sam/Desktop/")

Раскрытие - я автор этого пакета.

(Первые варианты, конечно, упоминаются в других ответах, здесь цель - показать, что glob использует os.scandir внутренне, и дайте им прямой ответ).


Использование glob

Как объяснялось ранее, с Python 3.5+ это просто:

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

#d:\temp\New folder
#d:\temp\New Text Document - Copy.txt
#d:\temp\New folder\New Text Document - Copy.txt
#d:\temp\New folder\New Text Document.txt

Использование pathlib

from pathlib import Path
for f in Path('d:/temp').glob('**/*'):
    print(f)

Использование os.scandir

os.scandir это что globделает внутренне. Итак, вот как это сделать напрямую, используя yield:

def listpath(path):
    for f in os.scandir(path):
        f2 = os.path.join(path, f)
        if os.path.isdir(f):
            yield f2
            yield from listpath(f2)
        else:
            yield f2

for f in listpath('d:\\temp'):
    print(f)

Вот адаптированная версия, которая позволяет glob.glob нравится функциональность без использования glob2,

def find_files(directory, pattern='*'):
    if not os.path.exists(directory):
        raise ValueError("Directory not found {}".format(directory))

    matches = []
    for root, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            full_path = os.path.join(root, filename)
            if fnmatch.filter([full_path], pattern):
                matches.append(os.path.join(root, filename))
    return matches

Так что, если у вас есть следующая структура DIR

tests/files
├── a0
│   ├── a0.txt
│   ├── a0.yaml
│   └── b0
│       ├── b0.yaml
│       └── b00.yaml
└── a1

Вы можете сделать что-то вроде этого

files = utils.find_files('tests/files','**/b0/b*.yaml')
> ['tests/files/a0/b0/b0.yaml', 'tests/files/a0/b0/b00.yaml']

Довольно много fnmatch сопоставление с образцом всего файла, а не только файла.

configfiles = glob.glob('C:/Users/sam/Desktop/**/*.txt")

Не работает во всех случаях, вместо этого используйте glob2

configfiles = glob2.glob('C:/Users/sam/Desktop/**/*.txt")

Если вы можете установить пакет glob2...

import glob2
filenames = glob2.glob("C:\\top_directory\\**\\*.ext")  # Where ext is a specific file extension
folders = glob2.glob("C:\\top_directory\\**\\")

Все имена файлов и папок:

all_ff = glob2.glob("C:\\top_directory\\**\\**")  

Если вы используете Python 3.4+, вы можете использовать pathlib модуль. Path.glob() метод поддерживает ** шаблон, который означает "этот каталог и все подкаталоги, рекурсивно". Возвращает генератор, уступающий Path объекты для всех соответствующих файлов.

from pathlib import Path
configfiles = Path("C:/Users/sam/Desktop/file1/").glob("**/*.txt")

Вы можете использовать функцию glob.glob() или glob.iglob() непосредственно из модуля glob для рекурсивного получения путей из каталогов / файлов и подкаталогов / подфайлов.

Синтаксис:

glob.glob(pathname, *, recursive=False) # pathname = '/path/to/the/directory' or subdirectory
glob.iglob(pathname, *, recursive=False)

В вашем примере можно написать так:

import glob

configfiles = [f for f in glob.glob("C:/Users/sam/Desktop/*.txt")]
for file in configfiles:
    print(file)

Команда rglobвыполнит бесконечную рекурсию на самом глубоком подуровне вашей структуры каталогов. Однако, если вам нужен только один уровень, не используйте его.

Я понимаю, что OP говорил об использовании glob.glob. Однако я считаю, что это отвечает намерению, которое заключается в рекурсивном поиске всех подпапок.

В rglobФункция недавно произвела 100-кратное увеличение скорости для алгоритма обработки данных, который использовал структуру папок в качестве фиксированного допущения для порядка чтения данных. Однако сrglob мы смогли выполнить однократное сканирование всех файлов в указанном родительском каталоге или ниже, сохранить их имена в списке (более миллиона файлов), а затем использовать этот список, чтобы определить, какие файлы нам нужно открыть в любой момент в future, основанный только на соглашениях об именах файлов в зависимости от того, в какой папке они были.

Как отметил Мартейн, глоб может делать это только через **Оператор введен в Python 3.5. Поскольку OP явно запрашивает модуль glob, следующее вернет ленивый итератор оценки, который ведет себя аналогично

import os, glob, itertools

configfiles = itertools.chain.from_iterable(glob.iglob(os.path.join(root,'*.txt'))
                         for root, dirs, files in os.walk('C:/Users/sam/Desktop/file1/'))

Обратите внимание, что вы можете повторить только один раз configfiles в этом подходе, хотя. Если вам требуется реальный список конфигурационных файлов, которые можно использовать в нескольких операциях, вам придется создать его явно с помощью list(configfiles),

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