Como posso pesquisar subpastas usando o módulo glob.glob?

107

Quero abrir uma série de subpastas em uma pasta e encontrar alguns arquivos de texto e imprimir algumas linhas dos arquivos de texto. Estou usando este:

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

Mas isso não pode acessar as subpastas também. Alguém sabe como posso usar o mesmo comando para acessar subpastas também?

UserYmY
fonte

Respostas:

163

No Python 3.5 e mais recente, use a nova **/funcionalidade recursiva :

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

Quando recursiveé definido, **seguido por um separador de caminho corresponde a 0 ou mais subdiretórios.

Em versões anteriores do Python, glob.glob()não é possível listar arquivos em subdiretórios recursivamente.

Nesse caso, eu usaria os.walk()combinado com 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')]

Isso percorrerá seus diretórios recursivamente e retornará todos os nomes de caminho absolutos aos .txtarquivos correspondentes . Neste caso específico , fnmatch.filter()pode ser um exagero, você também pode usar um .endswith()teste:

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')]
Martijn Pieters
fonte
3
Posso ver: glob.glob ('/ caminho para o diretório / * / *. Txt ") funcionando para mim. Bascialmente usando a regra do shell do Unix.
Surya
7
@ User123: isso não lista diretórios recursivamente . Você está listando todos os arquivos de texto em um nível de profundidade , mas não em outros subdiretórios ou mesmo diretamente em path to directory.
Martijn Pieters
1
Isso não está totalmente relacionado, mas por que a configuração recursive=Falsejunto com a **/ funcionalidade não fornece a lista de arquivos apenas na pasta dada, mas em seus filhos?
Dr_Zaszuś
@ Dr_Zaszuś: desculpe? **/fornece uma lista de nomes de diretório no diretório de trabalho atual, porque o padrão termina em /, e com recursive=Falsevocê basicamente tem um duplo *, correspondendo exatamente como */, apenas menos eficiente.
Martijn Pieters
@ Dr_Zaszuś: use */*se precisar de todos os arquivos em todos os subdiretórios.
Martijn Pieters
22

Para localizar arquivos em subdiretórios imediatos:

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

Para uma versão recursiva que atravessa todos os subdiretórios, você pode usar **e passar recursive=True desde Python 3.5 :

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

Ambas as funções chamam listas de retorno. Você pode usar glob.iglob()para retornar caminhos um por um. Ou usepathlib :

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

Ambos os métodos retornam iteradores (você pode obter caminhos um por um).

jfs
fonte
Sim, eu entendi isso; mas também não esperava glob()oferecer suporte a padrões em diretórios.
Martijn Pieters
Comentário excluído, vejo agora que deu uma impressão errada; além disso, o patch inclui uma atualização de documentação para o **caso de recursão. Mas para **funcionar, você tem que definir a recursion=Truechave, aliás.
Martijn Pieters
20

Há muita confusão neste tópico. Deixe-me ver se consigo esclarecer (Python 3.7):

  1. glob.glob('*.txt') :corresponde a todos os arquivos que terminam em '.txt' no diretório atual
  2. glob.glob('*/*.txt') :igual a 1
  3. glob.glob('**/*.txt') :corresponde a todos os arquivos que terminam em '.txt' apenas nos subdiretórios imediatos , mas não no diretório atual
  4. glob.glob('*.txt',recursive=True) :igual a 1
  5. glob.glob('*/*.txt',recursive=True) :igual a 3
  6. glob.glob('**/*.txt',recursive=True):corresponde a todos os arquivos que terminam em '.txt' no diretório atual e em todos os subdiretórios

Portanto, é melhor sempre especificar recursive=True.

germe
fonte
1
Esta deve ser a melhor resposta!
Abhik Sarkar
17

O pacote glob2 suporta curingas e é razoavelmente rápido

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

No meu laptop, leva aproximadamente 2 segundos para corresponder a > 60.000 caminhos de arquivo .

megawac
fonte
9

Você pode usar Formic com Python 2.6

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

Divulgação - eu sou o autor deste pacote.

Andrew Alcock
fonte
4

Aqui está uma versão adaptada que permite a glob.globfuncionalidade semelhante sem usar 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

Então, se você tiver a seguinte estrutura de dir

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

Você pode fazer algo assim

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

Praticamente a fnmatchcorrespondência de padrão no nome do arquivo inteiro, em vez de apenas no nome do arquivo.

cevaris
fonte
2

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

Não funciona para todos os casos, em vez disso, use glob2

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

Se você pode instalar o pacote glob2 ...

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

Todos os nomes de arquivos e pastas:

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

Se você estiver executando o Python 3.4+, pode usar o pathlibmódulo. O Path.glob()método suporta o **padrão, que significa “este diretório e todos os subdiretórios, recursivamente”. Ele retorna um gerador que produz Pathobjetos para todos os arquivos correspondentes.

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

Conforme apontado por Martijn, glob só pode fazer isso por meio do **operador introduzido no Python 3.5. Como o OP pediu explicitamente o módulo glob, o seguinte retornará um iterador de avaliação preguiçoso que se comporta de maneira semelhante

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

Observe que você só pode iterar uma vez configfilesnesta abordagem. Se você precisar de uma lista real de configfiles que podem ser usados ​​em várias operações, você terá que criá-la explicitamente usando list(configfiles).

f0xdx
fonte
0

O comando rglobfará uma recursão infinita no subnível mais profundo de sua estrutura de diretório. Se você deseja apenas um nível de profundidade, não o use.

Percebo que o OP estava falando sobre o uso de glob.glob. Acredito que isso atenda à intenção, no entanto, que é pesquisar todas as subpastas recursivamente.

A rglobfunção recentemente produziu um aumento de 100x na velocidade de um algoritmo de processamento de dados que estava usando a estrutura de pastas como uma suposição fixa para a ordem de leitura dos dados. No entanto, rglobconseguimos fazer uma única varredura em todos os arquivos em ou abaixo de um diretório pai especificado, salvar seus nomes em uma lista (mais de um milhão de arquivos) e, em seguida, usar essa lista para determinar quais arquivos precisávamos abrir em qualquer apontar no futuro com base nas convenções de nomenclatura de arquivo apenas em relação à pasta em que eles estavam.

brethvoice
fonte