Come usare glob () per trovare i file in modo ricorsivo?


738

Questo è quello che ho:

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

ma voglio cercare nelle sottocartelle di src. Qualcosa del genere funzionerebbe:

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

Ma questo è ovviamente limitato e goffo.

Risposte:


1355

Python 3.5+

Dato che sei su un nuovo Python, dovresti usare pathlib.Path.rglobdal pathlibmodulo.

from pathlib import Path

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

Se non vuoi usare pathlib, usa semplicemente glob.glob, ma non dimenticare di passare il recursiveparametro della parola chiave.

Per i casi in cui i file corrispondenti iniziano con un punto (.); come file nella directory corrente o file nascosti sul sistema basato su Unix, utilizzare la os.walksoluzione seguente.

Versioni precedenti di Python

Per le versioni precedenti di Python, utilizzare os.walkper ricorrere in modo ricorsivo a una directory e fnmatch.filterconfrontarsi con una semplice espressione:

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

3
Per Python di età superiore a 2.2 c'è os.path.walk()un po 'più complicato da usare rispetto aos.walk()
John La Rooy

20
@gnibbler So che è un vecchio commento, ma il mio commento è solo per far sapere alla gente che os.path.walk()è deprecato ed è stato rimosso in Python 3.
Pedro Cunha,

5
@DevC che potrebbe funzionare nel caso specifico posto in questa domanda, ma è facile immaginare qualcuno che vuole usarlo con domande come 'a * .c' ecc., Quindi penso che valga la pena mantenere l'attuale risposta piuttosto lenta.
Johan Dahlin,

2
Per quello che vale, nel mio caso la ricerca di oltre 10.000 file con glob è stata molto più lenta rispetto a os.walk, quindi ho scelto quest'ultima soluzione per quel motivo.
Godsmith,

2
Per python 3.4, pathlib.Path('src').glob('**/*.c')dovrebbe funzionare.
CivFan,

111

Simile ad altre soluzioni, ma usando fnmatch.fnmatch invece di glob, poiché os.walk ha già elencato i nomi dei file:

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

Inoltre, l'utilizzo di un generatore consente di elaborare ogni file così come viene trovato, anziché trovare tutti i file e quindi elaborarli.


3
perché 1-liner sono divertenti: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

Ho modificato il modulo glob per supportare ** per globbing ricorsivo, ad esempio:

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

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

Utile quando si desidera fornire agli utenti la possibilità di utilizzare la sintassi **, e quindi os.walk () da solo non è abbastanza buono.


2
Possiamo fermarci dopo aver trovato la prima partita? Forse rendere possibile usarlo come generatore piuttosto che farlo restituire un elenco di tutti i possibili risultati? Inoltre, questo è un DFS o un BFS? Preferirei di gran lunga un BFS, credo, in modo che i file vicini alla radice vengano trovati per primi. +1 per creare questo modulo e fornirlo su GitHub / pip.
ArtOfWarfare il

14
La sintassi ** è stata aggiunta al modulo glob ufficiale in Python 3.5.
ArtOfWarfare il

@ArtOfWarfare Va bene, bene. Questo è ancora utile per <3.5.
cs95,

1
Per attivare il globbing ricorsivo utilizzando **il modulo glob ufficiale, eseguire:glob(path, recursive=True)
winklerrr

68

A partire da Python 3.4, è possibile utilizzare il glob()metodo di una delle Pathclassi nel nuovo modulo pathlib , che supporta i **caratteri jolly. Per esempio:

from pathlib import Path

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

Aggiornamento: a partire da Python 3.5, è supportata anche la stessa sintassi glob.glob().


3
Anzi, e sarà in Python 3.5 . Doveva essere già così in Python 3.4, ma è stato omesso per errore .
taleinat


Nota che puoi anche usare pathlib.PurePath.relative_to in combinazione per ottenere percorsi relativi. Vedi la mia risposta qui per più contesto.
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

fnmatchti dà esattamente gli stessi schemi di glob, quindi questo è davvero un eccellente sostituto glob.globcon una semantica molto stretta. Una versione iterativa (ad es. Un generatore), IOW come sostituto glob.iglob, è un adattamento banale (solo yieldi risultati intermedi man mano che procedi, invece di inserire extendun singolo elenco di risultati per tornare alla fine).


1
Cosa ne pensi di usare recursive_glob(pattern, treeroot='.')come ho suggerito nella mia modifica? In questo modo, può essere chiamato ad esempio come recursive_glob('*.txt')e in modo intuitivo abbinare la sintassi di glob.
Chris Redford,

@ChrisRedford, lo vedo come un problema piuttosto secondario in entrambi i casi. Allo stato attuale, corrisponde all'ordine degli argomenti "files then pattern" fnmatch.filter, che è più o meno utile della possibilità di abbinare un singolo argomento glob.glob.
Alex Martelli,

25

Per Python> = 3.5 è possibile utilizzare **, recursive=True:

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

dimostrazione


Se ricorsivo è True, il modello ** corrisponderà a qualsiasi file e zero o più directoriesesubdirectories . Se il modello è seguito da un os.sep, solo directory e subdirectoriesmatch.


2
Funziona meglio di pathlib.Path ('./ path /'). Glob (' * / ') perché lo è anche nella cartella con dimensione 0
Charles Walker,

20

Ti consigliamo os.walkdi raccogliere i nomi dei file che corrispondono ai tuoi criteri. Per esempio:

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

15

Ecco una soluzione con comprensione dell'elenco nidificato os.walke corrispondenza del suffisso semplice anziché 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')]

Può essere compresso in una fodera:

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 generalizzato in funzione:

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 hai bisogno di globmodelli di stile completi , puoi seguire l'esempio di Alex e Bruno e usare 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')

7

Recentemente ho dovuto recuperare le mie foto con l'estensione .jpg. Ho eseguito photorec e ho recuperato 4579 directory all'interno di 2,2 milioni di file, con un'enorme varietà di estensioni. Con lo script seguente sono stato in grado di selezionare 50133 file con estensione .jpg in pochi minuti:

#!/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)

7

Prendere in considerazione pathlib.rglob().

Questo è come chiamare Path.glob()con l' "**/"aggiunta di fronte al modello relativo dato:

import pathlib


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

Vedi anche il post correlato di @ taleinat qui e un post simile altrove.


5

Johan e Bruno forniscono soluzioni eccellenti sui requisiti minimi indicati. Ho appena rilasciato Formic che implementa Ant FileSet e Globs in grado di gestire questo e scenari più complicati. Un'implementazione del tuo requisito è:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name

1
Il formale sembra essere abbandonato ?! E non supporta Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed

5

sulla base di altre risposte, questa è la mia attuale implementazione funzionante, che recupera i file XML nidificati in una directory radice:

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

Mi sto davvero divertendo con Python :)


3

Un altro modo per farlo usando solo il modulo glob. Basta seminare il metodo rglob con una directory di base iniziale e un modello da abbinare e restituirà un elenco di nomi di file corrispondenti.

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

3

Per Python 3.5 e versioni successive

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)

inoltre potresti aver bisogno

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'

3
La tua prima riga di codice non funziona per esaminare le sottodirectory. Ma se lo espandi semplicemente /**funziona per me, in questo modo:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack,

2

O con una comprensione dell'elenco:

 >>> 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") ] 

2

Appena fatto questo ... stamperà file e directory in modo gerarchico

Ma non ho usato fnmatch o 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)

2

Quello usa fnmatch o espressione regolare:

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

2

Oltre alle risposte suggerite, puoi farlo con una generazione pigra e una magia di comprensione dell'elenco:

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)

Oltre a inserirsi in una riga ed evitare elenchi inutili in memoria, questo ha anche il piacevole effetto collaterale, che puoi usarlo in un modo simile all'operatore **, ad esempio, puoi usare os.path.join(root, 'some/path/*.c')per ottenere tutti i file .c in tutto sottodirectory di src che hanno questa struttura.


2

Questo è un codice funzionante su Python 2.7. Come parte del mio lavoro devops, mi è stato richiesto di scrivere uno script che avrebbe spostato i file di configurazione contrassegnati con live-appName.properties in appName.properties. Potrebbero esserci altri file di estensione oltre a live-appName.xml.

Di seguito è riportato un codice funzionante per questo, che trova i file nelle directory indicate (livello nidificato) e quindi lo rinomina (sposta) nel nome file richiesto

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)

Questa funzione è chiamata da uno script principale

flipProperties(searchDir)

Spero che questo aiuti qualcuno alle prese con problemi simili.


1

Versione semplificata della risposta di Johan Dahlin, senza corrispondenza .

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

1

Ecco la mia soluzione che utilizza la comprensione dell'elenco per cercare ricorsivamente più estensioni di file in una directory e in tutte le sottodirectory:

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

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)

0

Ho modificato la risposta principale in questo post .. e recentemente ho creato questo script che eseguirà il ciclo di tutti i file in una determinata directory (searchdir) e le relative sottodirectory ... e stampa il nome del file, il rootdir, la data di modifica / creazione e dimensione.

Spero che questo aiuti qualcuno ... e che possano percorrere la directory e ottenere fileinfo.

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

0

Ecco una soluzione che abbinerà il modello al percorso completo e non solo al nome file di base.

Utilizza fnmatch.translate per convertire un modello in stile glob in un'espressione regolare, che viene quindi confrontata con il percorso completo di ciascun file trovato mentre si cammina nella directory.

re.IGNORECASEè facoltativo, ma auspicabile su Windows poiché il file system stesso non fa distinzione tra maiuscole e minuscole. (Non mi sono preoccupato di compilare il regex perché i documenti indicano che dovrebbe essere memorizzato nella 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

0

Avevo bisogno di una soluzione per Python 2.x che funzioni velocemente su directory di grandi dimensioni.
Concludo con questo:

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

Tieni presente che potresti aver bisogno di una gestione delle eccezioni nel caso in cui lsnon trovi alcun file corrispondente.


Ho appena capito che ls src/**/*.cfunziona solo se l'opzione globstar è abilitata ( shopt -s globstar) - vedi questa risposta per i dettagli.
Romano,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.