Используйте glob ** для сопоставления расширений файлов на основе рекурсивного параметра

#python #recursion #filesystems #glob

#python #рекурсия #файловые системы #глоб

Вопрос:

Я пытаюсь сопоставить файлы с заданным расширением, используя шаблон glob. Функция glob (из модуля glob) имеет параметр ‘recursive’, который, похоже, должен включать или выключать поиск в подкаталогах. Однако мне не удалось создать шаблон glob, который будет сопоставлять файлы определенного типа в текущем каталоге, только если recursive = False и в подкаталогах тоже, если recursive = True.

 folder='C:\test'
glob(folder '**.ext',recursive=True) #fails to find any files
glob(folder '\**.ext',recursive=True) #fails to search subfolders
glob(folder '\**',recursive=True) #fails to filter by type
glob(folder '\**\*.ext',recursive=False) #searches subfolders when it shouldn't
glob(folder '**\*.ext',recursive=True) #fails to search subfolders
  

На основе документации

«**» будет соответствовать любым файлам и нулю или более каталогов

Итак, я ожидал бы, что ** будет действовать как *, за исключением того, что он также соответствует косым чертам. Однако это работает не так, как ожидалось (см. Первые два примера выше).

Почему ** не работает должным образом и есть ли шаблон, который будет соответствовать определенным типам файлов, и рекурсию можно включить или выключить, используя только рекурсивный аргумент?

Ответ №1:

Я думаю, вы неправильно поняли значение recursive=True . Он используется для того, чтобы ** выполнять поиск в подкаталогах с переменной глубиной — glob(folder '\**\*.ext',recursive=True) будут найдены любые *.ext файлы в начальной папке или где-либо под ней. Без этого recursive=True , ** интерпретировалось бы обычным способом, причем каждое * значение соответствовало бы любому количеству символов, исключая , и ** поэтому было бы просто эквивалентно * — другими словами, glob(folder '\**\*.ext',recursive=False) выполнялся бы поиск любых *.ext файлов, которые находятся ровно на одном уровне подкаталога ниже начальной папки, но не в самой начальной папке или в более глубоких подкаталогах.

То, что именно вам следует использовать, будет зависеть от того, что вы пытаетесь найти — и действительно может быть, что вам нужно выбирать между различными случаями с помощью if инструкции — но нет варианта использования для использования ** с recursive=False , потому что в этом случае это просто эквивалентно * .

Наиболее вероятными полезными опциями являются:

 glob(folder '\*.ext',recursive=False)  # search in top folder only
glob(folder '\*\*.ext',recursive=False)  # search exactly one level down
glob(folder '\**\*.ext',recursive=True)  # search to arbitrary depth
  

,recursive=False Можно опустить, поскольку он используется по умолчанию.

Если бы было ровно два варианта, из которых вы хотели выбрать логический вариант, вы всегда могли бы написать свою собственную функцию-оболочку, например:

 def myglob(folder, pattern, subdirs=False):
    if subdirs:
        return glob(f'{folder}\**\{pattern}', recursive=True)
    else
        return glob(f'{folder}\{pattern}')

print(myglob(folder, '*.ext', subdirs=False))
print(myglob(folder, '*.ext', subdirs=True))
print(myglob(folder, '*.ext'))  # same as subdirs=False in this example
  

(В Unix-подобных операционных системах, прочитайте / для выше.)

Комментарии:

1. Спасибо, я все еще не понимаю, почему первые два примера, которые я привел, не работают? Почему я не могу сделать что-то вроде третьего (который работает как ожидалось, переключаясь на основе значения recursive), а также фильтруя для данного расширения файла?

2. @KalevMaricq Насколько я могу судить, похоже, что, хотя ** with recursive=True предназначено для сопоставления с любым количеством элементов пути, оно не будет соответствовать частям элементов пути — так что вам не следует делать **.ext как часть шаблона glob.

Ответ №2:

Операция glob ‘**’

Исходный код glob можно найти здесь. Кроме комментариев, ‘**’ появляется только в _isrecursive функции:

 def _isrecursive(pattern):
    if isinstance(pattern, bytes):
        return pattern == b'**'
    else:
        return pattern == '**'
  

Поскольку _isrecursive функция проверяет наличие ‘‘ с помощью оператора равенства, ‘‘ будет работать по назначению, только если это полный ‘шаблон’. _isrecursive функция вызывается 4 раза в исходном коде, один раз для всего глобуса и 3 раза для ‘basename’, который определяется как:

 dirname, basename = os.path.split(pathname)
  

Это вызывается рекурсивно, разделяя хвосты пути по одному за раз, и каждый раз базовое имя — это все, что следует за косой чертой (технически os.sep, которая является для Windows или / для POSIX). Это означает, что для того, чтобы ‘**’ работало так, как описано, оно не может располагаться рядом ни с чем, кроме косых черт.

Если для параметра recursive явно не задано значение True, ‘**’ = ‘*’ ‘*’ = ‘*’ поскольку ‘*’ соответствует 0 или более символам без косой черты.

Сопоставление расширений

Из-за того, как работает ‘**’, он должен использоваться для представления всего уровня глобуса. Поскольку он представляет 0 или более уровней, r’folder***.ext’ будет сопоставлять файлы в ‘папке’, а также вложенные папки. Однако, без рекурсивного значения true, этот шаблон будет сопоставлять только файлы первого уровня вложенных папок.

‘**.ext’ не будет работать, потому что ‘**’ не одинок. Это оставляет следующие параметры:

  1. Используйте другую библиотеку (например, os.walk, pathlib.glob и т.д.)

  2. [f for f in glob(folder '\**',recursive=subdirs) if f.endswith('.ext')]

  3. glob(folder ('\**' if subdirs else '') '\*.ext, recursive=True)

    Или эквивалентно,

     if recursive:
        glob(folder '\**\*.ext', recursive=True)
    else:
        glob(folder '\*.ext')
      
  4. glob(folder '\..\**\*.ext', recursive=subdirs) #not recommended since it will also match files in sibling folders