Como usar glob () para encontrar arquivos recursivamente?

738

Isto é o que eu tenho:

glob(os.path.join('src','*.c'))

mas quero pesquisar as subpastas de src. Algo assim funcionaria:

glob(os.path.join('src','*.c'))
glob(os.path.join('src','*','*.c'))
glob(os.path.join('src','*','*','*.c'))
glob(os.path.join('src','*','*','*','*.c'))

Mas isso é obviamente limitado e desajeitado.

Ben Gartner
fonte

Respostas:

1355

Python 3.5 ou superior

Como você está em um novo python, use pathlib.Path.rglobo pathlibmódulo

from pathlib import Path

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

Se você não quiser usar o pathlib, use glob.glob, mas não esqueça de passar o recursiveparâmetro keyword.

Para casos em que arquivos correspondentes começam com um ponto (.); como arquivos no diretório atual ou arquivos ocultos no sistema baseado em Unix, use a os.walksolução abaixo.

Versões anteriores do Python

Para versões mais antigas do Python, use os.walkpara percorrer recursivamente um diretório e fnmatch.filtercomparar com uma expressão simples:

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))
Johan Dahlin
fonte
3
Para Python mais velha do que 2,2 não é os.path.walk()que é um pouco mais complicadas de usar do queos.walk()
John La Rooy
20
@gnibbler eu sei que é um comentário de idade, mas meu comentário é apenas para que as pessoas saibam que os.path.walk()é obsoleto e foi removido em Python 3.
Pedro Cunha
5
O @DevC que pode funcionar no caso específico desta pergunta, mas é fácil imaginar alguém que queira usá-lo com consultas como 'a * .c' etc, então acho que vale a pena manter a resposta um pouco lenta atual.
Johan Dahlin
2
Pelo que vale a pena, no meu caso, encontrar mais de 10.000 arquivos com glob era muito mais lento do que com os.walk, então fui com a última solução por esse motivo.
Godsmith 12/09/18
2
Para python 3.4, pathlib.Path('src').glob('**/*.c')deve funcionar.
precisa saber é o seguinte
111

Semelhante a outras soluções, mas usando fnmatch.fnmatch em vez de glob, pois o os.walk já listou os nomes de arquivo:

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

Além disso, o uso de um gerador permite processar cada arquivo conforme ele é encontrado, em vez de encontrar todos os arquivos e processá-los.

Bruno Oliveira
fonte
3
porque 1-liners são divertidos: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 01/08
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

Modifiquei o módulo glob para oferecer suporte a ** para globbing recursivo, por exemplo:

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

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

Útil quando você deseja fornecer a seus usuários a capacidade de usar a sintaxe ** e, portanto, os.walk () por si só não é bom o suficiente.

miracle2k
fonte
2
Podemos fazer essa parada depois de encontrar a primeira correspondência? Talvez possibilite usá-lo como gerador, em vez de retornar uma lista de todos os resultados possíveis? Além disso, isso é um DFS ou um BFS? Eu prefiro um BFS, eu acho, para que os arquivos que estão perto da raiz sejam encontrados primeiro. +1 para criar este módulo e fornecê-lo no GitHub / pip.
ArtOfWarfare
14
A sintaxe ** foi adicionada ao módulo glob oficial no Python 3.5.
ArtOfWarfare 26/01
@ArtOfWarfare Tudo bem, tudo bem. Isso ainda é útil para <3,5.
cs95
1
Para ativar o globbing recursivo usando **o módulo glob oficial, faça:glob(path, recursive=True)
winklerrr
68

A partir do Python 3.4, é possível usar o glob()método de uma das Pathclasses no novo módulo pathlib , que suporta **curingas. Por exemplo:

from pathlib import Path

for file_path in Path('src').glob('**/*.c'):
    print(file_path) # do whatever you need with these files

Atualização: A partir do Python 3.5, a mesma sintaxe também é suportada por glob.glob().

taleinat
fonte
3
De fato, e será no Python 3.5 . Era para já ser assim no Python 3.4, mas foi omitido por engano .
taleinat
Essa sintaxe agora é suportada pelo glob.glob () a partir do Python 3.5 .
Taleinat 4/08/15
Observe que você também pode usar pathlib.PurePath.relative_to em combinação para obter caminhos relativos. Veja minha resposta aqui para mais contexto.
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

fnmatchfornece exatamente os mesmos padrões de glob, portanto, esse é realmente um excelente substituto para glob.globa semântica muito próxima. Uma versão iterativa (por exemplo, um gerador), substituída por IOW glob.iglob, é uma adaptação trivial (apenas yieldos resultados intermediários à medida que você avança, em vez de extenduma única lista de resultados para retornar no final).

Alex Martelli
fonte
1
O que você acha de usar recursive_glob(pattern, treeroot='.')como sugeri na minha edição? Dessa forma, pode ser chamado, por exemplo, como recursive_glob('*.txt')e intuitivamente corresponde à sintaxe de glob.
Chris Redford
@ ChrisRedford, eu vejo isso como uma questão bem menor de qualquer maneira. Como está agora, ele corresponde à ordem de argumento "arquivos e padrões" de fnmatch.filter, o que é aproximadamente tão útil quanto a possibilidade de correspondência de argumento único glob.glob.
Alex Martelli
25

Para python> = 3,5 você pode usar **, recursive=True:

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

Demo


Se recursivo for True, o padrão ** corresponderá a todos os arquivos e zero ou mais directoriesesubdirectories . Se o padrão for seguido por um os.sep, apenas diretórios e subdirectoriescorrespondência.

CONvid19
fonte
2
Isso funciona melhor que pathlib.Path ('./ path /'). Glob (' * / ') porque também ocorre na pasta com tamanho 0
Charles Walker
20

Você deseja usar os.walkpara coletar nomes de arquivos que correspondem aos seus critérios. Por exemplo:

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))
Geoff Reedy
fonte
15

Aqui está uma solução com compreensão de lista aninhada os.walke correspondência simples de sufixo em vez de 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')]

Pode ser compactado em uma linha:

import os;cfiles=[os.path.join(r,f) for r,d,fs in os.walk('src') for f in fs if f.endswith('.c')]

ou generalizada como uma função:

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

Se você precisa de globpadrões de estilo completos , pode seguir o exemplo de Alex e Bruno e usar 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
fonte
7

Recentemente tive que recuperar minhas fotos com a extensão .jpg. Eu executei o photorec e recuperei 4579 diretórios em 2,2 milhões de arquivos, com uma enorme variedade de extensões. Com o script abaixo, eu fui capaz de selecionar 50133 arquivos com a extensão .jpg em questão de minutos:

#!/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)
Mustafa Çetin
fonte
7

Considere pathlib.rglob().

É como chamar Path.glob()com "**/"adicionado na frente do padrão relativo fornecido:

import pathlib


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

Veja também @ relacionado de taleinat pós aqui e um semelhante pós em outro lugar.

pylang
fonte
5

Johan e Bruno fornecem excelentes soluções para os requisitos mínimos, conforme indicado. Acabo lançado fórmico que implementa Ant conjunto de arquivos e Globs que podem lidar com isso e cenários mais complicados. Uma implementação de sua exigência é:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Andrew Alcock
fonte
1
Formic parece estar abandonado ?! E ele não suporta Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed
5

com base em outras respostas, esta é minha implementação de trabalho atual, que recupera arquivos xml aninhados em um diretório raiz:

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

Estou realmente me divertindo com python :)

daveoncode
fonte
3

Outra maneira de fazer isso usando apenas o módulo glob. Apenas propague o método rglob com um diretório base inicial e um padrão para corresponder e ele retornará uma lista de nomes de arquivos correspondentes.

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
chris-piekarski
fonte
3

Para python 3.5 e posterior

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)

mais você pode precisar

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'
Sami
fonte
3
Sua primeira linha de código não funciona para procurar subdiretórios. Mas se você apenas expandi-lo por /**ele trabalha para mim, assim:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack
2

Ou com uma compreensão da lista:

 >>> 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
fonte
2

Acabei de fazer isso .. ele irá imprimir arquivos e diretórios de maneira hierárquica

Mas eu não usei fnmatch ou 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)
Shaurya Gupta
fonte
2

Aquele usa fnmatch ou expressão regular:

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
fonte
2

Além das respostas sugeridas, você pode fazer isso com alguma mágica de geração lenta e compreensão de lista:

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)

Além de caber em uma linha e evitar listas desnecessárias na memória, isso também tem um bom efeito colateral, que você pode usá-lo de maneira semelhante ao operador **, por exemplo, você pode usar os.path.join(root, 'some/path/*.c')para obter todos os arquivos .c subdiretórios do src que possuem essa estrutura.

f0xdx
fonte
2

Este é um código que funciona no Python 2.7. Como parte do meu trabalho de devops, fui obrigado a escrever um script que moveria os arquivos de configuração marcados com live-appName.properties para appName.properties. Pode haver outros arquivos de extensão, como o live-appName.xml.

Abaixo está um código funcional para isso, que localiza os arquivos nos diretórios fornecidos (nível aninhado) e depois o renomeia (move) para o nome de arquivo necessário

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)

Esta função é chamada de um script principal

flipProperties(searchDir)

Espero que isso ajude alguém com problemas semelhantes.

Sanjay Bharwani
fonte
1

Versão simplificada da resposta de Johan Dahlin, sem 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']
Flui livremente
fonte
1

Aqui está minha solução usando a compreensão de lista para procurar várias extensões de arquivo recursivamente em um diretório e em todos os subdiretórios:

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
fonte
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
fonte
0

Eu modifiquei a resposta principal nesta postagem .. e criei recentemente esse script que percorrerá todos os arquivos em um determinado diretório (searchdir) e os subdiretórios abaixo dele ... e imprime o nome do arquivo, rootdir, data de modificação / criação e Tamanho.

Espero que isso ajude alguém ... e eles podem percorrer o diretório e obter informações sobre o arquivo.

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
fonte
0

Aqui está uma solução que corresponderá ao padrão no caminho completo e não apenas no nome do arquivo base.

Ele é usado fnmatch.translatepara converter um padrão de estilo glob em uma expressão regular, que é comparada com o caminho completo de cada arquivo encontrado ao percorrer o diretório.

re.IGNORECASEé opcional, mas desejável no Windows, pois o próprio sistema de arquivos não diferencia maiúsculas de minúsculas. (Não me preocupei em compilar o regex porque os documentos indicam que ele deve ser armazenado em cache internamente.)

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
yoyo
fonte
0

Eu precisava de uma solução para python 2.x que funcionasse rapidamente em diretórios grandes.
Termino com isso:

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

Observe que você pode precisar de algum tratamento de exceção, caso lsnão encontre nenhum arquivo correspondente.

romano
fonte
Acabei de perceber que ls src/**/*.csó funciona se a opção globstar estiver ativada ( shopt -s globstar) - veja esta resposta para obter detalhes.
Roman