¿Cómo usar glob () para buscar archivos de forma recursiva?

738

Esto es lo que tengo:

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

pero quiero buscar en las subcarpetas de src. Algo como esto funcionaría:

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

Pero esto es obviamente limitado y torpe.

Ben Gartner
fuente

Respuestas:

1355

Python 3.5+

Como estás en una nueva python, debes usarla pathlib.Path.rglobdesde el pathlibmódulo.

from pathlib import Path

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

Si no desea usar pathlib, solo use glob.glob, pero no olvide pasar el recursiveparámetro de palabra clave.

Para los casos en que los archivos coincidentes comienzan con un punto (.); como archivos en el directorio actual o archivos ocultos en el sistema basado en Unix, use la os.walksolución a continuación.

Versiones antiguas de Python

Para versiones anteriores de Python, use os.walkpara recorrer recursivamente un directorio y fnmatch.filterhacer coincidir una expresión simple:

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
fuente
3
Para Python mayores de 2.2, hay os.path.walk()un poco más complicado de usar queos.walk()
John La Rooy
20
@gnibbler Sé que es un comentario antiguo, pero mi comentario es solo para que la gente sepa que os.path.walk()está en desuso y se ha eliminado en Python 3.
Pedro Cunha
55
@DevC que podría funcionar en el caso específico de esta pregunta, pero es fácil imaginar a alguien que quiera usarlo con consultas como 'a * .c', etc., por lo que creo que vale la pena mantener la respuesta actual algo lenta.
Johan Dahlin
2
Por lo que vale, en mi caso encontrar más de 10,000 archivos con glob fue mucho más lento que con os.walk, así que elegí la última solución por ese motivo.
Godsmith
2
Para python 3.4, pathlib.Path('src').glob('**/*.c')debería funcionar.
CivFan
111

Similar a otras soluciones, pero usando fnmatch.fnmatch en lugar de glob, ya que os.walk ya enumeró los nombres de archivo:

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

Además, el uso de un generador le permite procesar cada archivo tal como se encuentra, en lugar de buscar todos los archivos y luego procesarlos.

Bruno Oliveira
fuente
3
porque 1-liners son 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
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

Modifiqué el módulo glob para admitir ** para el globbing recursivo, por ejemplo:

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

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

Útil cuando desea proporcionar a sus usuarios la capacidad de usar la sintaxis **, y por lo tanto os.walk () por sí solo no es lo suficientemente bueno.

miracle2k
fuente
2
¿Podemos hacer esta parada después de que encuentre el primer partido? ¿Tal vez sea posible usarlo como generador en lugar de hacer que devuelva una lista de todos los resultados posibles? Además, ¿es esto un DFS o un BFS? Creo que preferiría un BFS, para que los archivos que están cerca de la raíz se encuentren primero. +1 para crear este módulo y proporcionarlo en GitHub / pip.
ArtOfWarfare
14
La sintaxis ** se agregó al módulo oficial glob en Python 3.5.
ArtOfWarfare
@ArtOfWarfare Muy bien, bien. Esto sigue siendo útil para <3.5.
cs95
1
Para activar el globing recursivo **con el módulo glob oficial, haga:glob(path, recursive=True)
winklerrr
68

Comenzando con Python 3.4, uno puede usar el glob()método de una de las Pathclases en el nuevo módulo pathlib , que admite **comodines. Por ejemplo:

from pathlib import Path

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

Actualización: a partir de Python 3.5, la misma sintaxis también es compatible glob.glob().

taleinat
fuente
3
De hecho, y estará en Python 3.5 . Se suponía que ya era así en Python 3.4, pero se omitió por error .
taleinat
Esta sintaxis ahora es compatible con glob.glob () a partir de Python 3.5 .
taleinat
Tenga en cuenta que también puede usar pathlib.PurePath.relative_to en combinación para obtener rutas relativas. Vea mi respuesta aquí para más 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

fnmatchle ofrece exactamente los mismos patrones glob, por lo que este es realmente un excelente reemplazo para glob.globuna semántica muy cercana. Una versión iterativa (p. Ej., Un generador), para la cual IOW es un reemplazo glob.iglob, es una adaptación trivial (solo yieldlos resultados intermedios a medida que avanza, en lugar de extenduna sola lista de resultados para regresar al final).

Alex Martelli
fuente
1
¿Qué piensas sobre usar recursive_glob(pattern, treeroot='.')como sugerí en mi edición? De esta manera, se puede llamar, por ejemplo, recursive_glob('*.txt')y coincidir intuitivamente con la sintaxis de glob.
Chris Redford
@ ChrisRedford, lo veo como un problema bastante menor de cualquier manera. Tal como está ahora, coincide con el orden de argumento "archivos y luego patrón" fnmatch.filter, que es aproximadamente tan útil como la posibilidad de hacer coincidir un solo argumento glob.glob.
Alex Martelli
25

Para pitón> = 3.5 se puede utilizar **, recursive=True:

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

Manifestación


Si es recursivo True, el patrón ** coincidirá con cualquier archivo y cero o más directoriesysubdirectories . Si el patrón es seguido por un os.sep, solo directorios y subdirectoriescoincide.

CONvid19
fuente
2
Esto funciona mejor que pathlib.Path ('./ path /'). Glob (' * / ') porque también lo hace en una carpeta con un tamaño de 0
Charles Walker
20

Deberá utilizar os.walkpara recopilar nombres de archivo que coincidan con sus criterios. Por ejemplo:

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
fuente
15

Aquí hay una solución con comprensiones de listas anidadas os.walky coincidencia de sufijos simple en lugar 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')]

Se puede comprimir a una sola línea:

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

o generalizado como una función:

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

Si necesita globpatrones de estilo completos , puede seguir el ejemplo de Alex y Bruno y 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
fuente
7

Recientemente tuve que recuperar mis fotos con la extensión .jpg. Ejecuté photorec y recuperé 4579 directorios de 2.2 millones de archivos, con una gran variedad de extensiones. Con el script a continuación pude seleccionar 50133 archivos con extensión .jpg en 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
fuente
7

Considere pathlib.rglob().

Esto es como llamar Path.glob()con "**/"agregado en frente del patrón relativo dado:

import pathlib


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

Ver también relacionada @ de taleinat puesto aquí y uno similar puesto en otro lugar.

pylang
fuente
5

Johan y Bruno brindan soluciones excelentes en el requisito mínimo como se indicó. Acabo de lanzar Formic que implementa Ant FileSet y Globs que pueden manejar este y otros escenarios más complicados. Una implementación de su requerimiento es:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Andrew Alcock
fuente
1
¿Formic parece estar abandonado? Y no es compatible con Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
azulado el
5

basado en otras respuestas, esta es mi implementación de trabajo actual, que recupera archivos xml anidados en un directorio raíz:

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

Realmente me estoy divirtiendo con python :)

daveoncode
fuente
3

Otra forma de hacerlo usando solo el módulo glob. Simplemente siembra el método rglob con un directorio base inicial y un patrón que coincida, y devolverá una lista de nombres de archivos coincidentes.

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
fuente
3

Para python 3.5 y 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)

aún más podría necesitar

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
fuente
3
Su primera línea de código no funciona para buscar subdirectorios. Pero si solo lo /**file_names_array = glob.glob('src/**/*.c', recursive=True)
expandes
2

O con una lista de comprensión:

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

Acabo de hacer esto ... imprimirá archivos y directorios de forma jerárquica

Pero no usé fnmatch ni caminé

#!/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
fuente
2

Ese usa fnmatch o expresión 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
fuente
2

Además de las respuestas sugeridas, puedes hacer esto con algo de generación perezosa y magia de comprensión de listas:

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)

Además de encajar en una línea y evitar listas innecesarias en la memoria, esto también tiene el agradable efecto secundario, que puede usarlo de manera similar al operador **, por ejemplo, podría usarlo os.path.join(root, 'some/path/*.c')para obtener todos los archivos .c en todos subdirectorios de src que tienen esta estructura.

f0xdx
fuente
2

Este es un código de trabajo en Python 2.7. Como parte de mi trabajo devops, se me pidió que escribiera un script que moviera los archivos de configuración marcados con live-appName.properties a appName.properties. Podría haber otros archivos de extensión, como live-appName.xml.

A continuación hay un código de trabajo para esto, que encuentra los archivos en los directorios dados (nivel anidado) y luego los renombra (mueve) al nombre de archivo requerido

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 función se llama desde un script principal

flipProperties(searchDir)

Espero que esto ayude a alguien que lucha con problemas similares.

Sanjay Bharwani
fuente
1

Versión simplificada de la respuesta de Johan Dahlin, sin 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']
fluir libremente
fuente
1

Aquí está mi solución usando la comprensión de listas para buscar múltiples extensiones de archivos de forma recursiva en un directorio y todos los subdirectorios:

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

Modifiqué la respuesta principal en esta publicación ... y recientemente creé este script que recorrerá todos los archivos en un directorio dado (searchdir) y los subdirectorios debajo de él ... e imprime el nombre del archivo, rootdir, fecha de modificación / creación, y Talla.

Espero que esto ayude a alguien ... y que puedan recorrer el directorio y obtener información de archivo.

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

Aquí hay una solución que hará coincidir el patrón con la ruta completa y no solo con el nombre de archivo base.

Se utiliza fnmatch.translatepara convertir un patrón de estilo glob en una expresión regular, que luego se compara con la ruta completa de cada archivo encontrado al recorrer el directorio.

re.IGNORECASEes opcional, pero deseable en Windows ya que el sistema de archivos en sí no distingue entre mayúsculas y minúsculas. (No me molesté en compilar la expresión regular porque los documentos indican que debe almacenarse en caché 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
yoyó
fuente
0

Necesitaba una solución para python 2.x que funcione rápido en directorios grandes.
Me comprometo con esto:

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

Tenga en cuenta que es posible que necesite un manejo de excepciones en caso de lsque no encuentre ningún archivo coincidente.

romano
fuente
Me acabo de dar cuenta de que ls src/**/*.csolo funciona si la opción globstar está habilitada ( shopt -s globstar) - mira esta respuesta para más detalles.
Romano