Comment utiliser glob () pour rechercher des fichiers récursivement?

738

Voici ce que j'ai:

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

mais je veux rechercher les sous-dossiers de src. Quelque chose comme ça fonctionnerait:

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

Mais c'est évidemment limité et maladroit.

Ben Gartner
la source

Réponses:

1355

Python 3.5+

Puisque vous êtes sur un nouveau python, vous devez utiliser à pathlib.Path.rglobpartir du pathlibmodule.

from pathlib import Path

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

Si vous ne voulez pas utiliser pathlib, utilisez simplement glob.glob, mais n'oubliez pas de passer le recursiveparamètre de mot - clé.

Pour les cas où les fichiers correspondants commençant par un point (.); comme les fichiers du répertoire courant ou les fichiers cachés sur un système basé sur Unix, utilisez la os.walksolution ci-dessous.

Versions Python plus anciennes

Pour les anciennes versions de Python, utilisez os.walkpour parcourir récursivement un répertoire et fnmatch.filterpour faire correspondre une expression 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
la source
3
Pour Python plus ancien que 2.2, il y en a un os.path.walk()qui est un peu plus délicat à utiliser queos.walk()
John La Rooy
20
@gnibbler Je sais que c'est un vieux commentaire, mais mon commentaire est juste de faire savoir aux gens qu'il os.path.walk()est obsolète et a été supprimé en Python 3.
Pedro Cunha
5
@DevC qui pourrait fonctionner dans le cas spécifique posé dans cette question, mais il est facile d'imaginer quelqu'un qui veut l'utiliser avec des requêtes telles que `` a * .c '', etc., donc je pense que cela vaut la peine de garder la réponse actuelle un peu lente.
Johan Dahlin
2
Pour ce que ça vaut, dans mon cas, trouver plus de 10 000 fichiers avec glob était beaucoup plus lent qu'avec os.walk, alors j'ai opté pour cette dernière solution pour cette raison.
Godsmith
2
Pour python 3.4, pathlib.Path('src').glob('**/*.c')devrait fonctionner.
CivFan
111

Similaire à d'autres solutions, mais en utilisant fnmatch.fnmatch au lieu de glob, car os.walk a déjà répertorié les noms de fichiers:

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

De plus, l'utilisation d'un générateur vous permet de traiter chaque fichier tel qu'il est trouvé, au lieu de rechercher tous les fichiers , puis de les traiter.

Bruno Oliveira
la source
3
parce que les 1-liners sont amusants: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

J'ai modifié le module glob pour prendre en charge ** la globalisation récursive, par exemple:

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

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

Utile lorsque vous souhaitez donner à vos utilisateurs la possibilité d'utiliser la syntaxe **, et donc os.walk () seul n'est pas suffisant.

miracle2k
la source
2
Pouvons-nous faire cet arrêt après avoir trouvé le premier match? Peut-être permettre de l'utiliser comme générateur plutôt que de lui renvoyer une liste de tous les résultats possibles? Est-ce aussi un DFS ou un BFS? Je préfère de loin un BFS, je pense, pour que les fichiers proches de la racine soient trouvés en premier. +1 pour avoir créé ce module et l'avoir fourni sur GitHub / pip.
ArtOfWarfare
14
La syntaxe ** a été ajoutée au module glob officiel de Python 3.5.
ArtOfWarfare
@ArtOfWarfare Très bien, très bien. Ceci est toujours utile pour <3.5.
cs95
1
Pour activer la globalisation récursive en utilisant **avec le module officiel de glob, faites:glob(path, recursive=True)
winklerrr
68

À partir de Python 3.4, on peut utiliser la glob()méthode de l'une des Pathclasses du nouveau module pathlib , qui prend en charge les **caractères génériques. Par exemple:

from pathlib import Path

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

Mise à jour: à partir de Python 3.5, la même syntaxe est également prise en charge par glob.glob().

taleinat
la source
3
En effet, et ce sera en Python 3.5 . Il était censé l'être déjà dans Python 3.4, mais a été omis par erreur .
taleinat
Cette syntaxe est désormais prise en charge par glob.glob () à partir de Python 3.5 .
taleinat
Notez que vous pouvez également utiliser pathlib.PurePath.relative_to en combinaison pour obtenir des chemins relatifs. Voir ma réponse ici pour plus de contexte.
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

fnmatchvous donne exactement les mêmes modèles que glob, donc c'est vraiment un excellent remplacement pour glob.globune sémantique très proche. Une version itérative (par exemple un générateur), IOW en remplacement glob.iglob, est une adaptation triviale (juste yieldles résultats intermédiaires au fur et à mesure, au lieu de extendgénérer une seule liste de résultats à retourner à la fin).

Alex Martelli
la source
1
Que pensez-vous de l'utilisation recursive_glob(pattern, treeroot='.')comme je l'ai suggéré dans mon montage? De cette façon, il peut être appelé par exemple comme recursive_glob('*.txt')et correspondre intuitivement à la syntaxe de glob.
Chris Redford
@ChrisRedford, je le vois comme un problème assez mineur de toute façon. Dans l'état actuel des choses, il correspond à l'ordre d'argument "files then pattern" de fnmatch.filter, ce qui est à peu près aussi utile que la possibilité de faire correspondre un seul argument glob.glob.
Alex Martelli
25

Pour python> = 3.5 , vous pouvez utiliser **, recursive=True:

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

Démo


Si récursif est True, le modèle ** correspondra à tous les fichiers et à zéro ou plus directoriesetsubdirectories . Si le motif est suivi d'unos.sep , seuls les répertoires et les subdirectoriescorrespondances.

CONvid19
la source
2
Cela fonctionne mieux que pathlib.Path ('./ path /'). Glob (' * / ') car il en est de même dans le dossier de taille 0
Charles Walker
20

Vous voudrez utiliser os.walkpour collecter des noms de fichiers qui correspondent à vos critères. Par exemple:

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
la source
15

Voici une solution avec des listes de listes imbriquées os.walket une correspondance de suffixe simple au lieu 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')]

Il peut être compressé en une seule ligne:

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 généralisé en fonction:

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 vous avez besoin de globmodèles de style complet , vous pouvez suivre l'exemple d'Alex et de Bruno et utiliser 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
la source
7

Récemment, j'ai dû récupérer mes photos avec l'extension .jpg. J'ai couru photorec et récupéré 4579 répertoires dans 2,2 millions de fichiers, avec une grande variété d'extensions. Avec le script ci-dessous, j'ai pu sélectionner 50133 fichiers havin .jpg extension en quelques minutes:

#!/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
la source
7

Considérez pathlib.rglob().

C'est comme appeler Path.glob()avec "**/"ajouté devant le modèle relatif donné:

import pathlib


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

Voir aussi le post connexe de @ taleinat ici et un post similaire ailleurs.

pylang
la source
5

Johan et Bruno fournissent d'excellentes solutions sur l'exigence minimale comme indiqué. Je viens de publier Formic qui implémente Ant FileSet et Globs qui peuvent gérer cela et des scénarios plus compliqués. Une implémentation de votre exigence est:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Andrew Alcock
la source
1
Le formel semble abandonné?! Et il ne prend pas en charge Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed le
5

sur la base d'autres réponses, voici mon implémentation de travail actuelle, qui récupère les fichiers xml imbriqués dans un répertoire racine:

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

Je m'amuse vraiment avec python :)

daveoncode
la source
3

Une autre façon de le faire en utilisant uniquement le module glob. Il suffit d'amorcer la méthode rglob avec un répertoire de base de départ et un modèle pour correspondre et il renverra une liste de noms de fichiers correspondants.

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
la source
3

Pour python 3.5 et versions ultérieures

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)

plus vous pourriez avoir besoin

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
la source
3
Votre première ligne de code ne fonctionne pas pour la recherche dans les sous-répertoires. Mais si vous le développez simplement, /**cela fonctionne pour moi, comme ça:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack
2

Ou avec une compréhension de la liste:

 >>> 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
la source
2

Je viens de faire cela .. il imprimera les fichiers et le répertoire de manière hiérarchique

Mais je n'ai pas utilisé 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
la source
2

Celui-là utilise fnmatch ou expression régulière:

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
la source
2

En plus des réponses suggérées, vous pouvez le faire avec une génération de paresseux et une magie de compréhension de liste:

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)

En plus de tenir sur une seule ligne et d'éviter les listes inutiles en mémoire, cela a aussi le bel effet secondaire, que vous pouvez l'utiliser d'une manière similaire à l'opérateur **, par exemple, vous pouvez utiliser os.path.join(root, 'some/path/*.c')pour obtenir tous les fichiers .c en tout sous-répertoires de src qui ont cette structure.

f0xdx
la source
2

Il s'agit d'un code de travail sur Python 2.7. Dans le cadre de mon travail de devops, je devais écrire un script qui déplacerait les fichiers de configuration marqués avec live-appName.properties vers appName.properties. Il pourrait y avoir d'autres fichiers d'extension comme live-appName.xml.

Voici un code de travail pour cela, qui trouve les fichiers dans les répertoires donnés (niveau imbriqué) puis le renomme (déplace) au nom de fichier requis

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)

Cette fonction est appelée à partir d'un script principal

flipProperties(searchDir)

J'espère que cela aidera quelqu'un aux prises avec des problèmes similaires.

Sanjay Bharwani
la source
1

Version simplifiée de la réponse de Johan Dahlin, sans 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']
sans débit
la source
1

Voici ma solution en utilisant la compréhension de liste pour rechercher plusieurs extensions de fichiers récursivement dans un répertoire et tous les sous-répertoires:

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
la source
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
la source
0

J'ai modifié la première réponse dans cette publication .. et récemment créé ce script qui parcourra tous les fichiers dans un répertoire donné (searchdir) et les sous-répertoires en dessous ... et imprime le nom de fichier, rootdir, la date de modification / création et Taille.

J'espère que cela aide quelqu'un ... et qu'il pourra parcourir le répertoire et obtenir 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))
ihightower
la source
0

Voici une solution qui fera correspondre le modèle au chemin complet et pas seulement au nom de fichier de base.

Il utilise fnmatch.translatepour convertir un modèle de style glob en une expression régulière, qui est ensuite comparée au chemin complet de chaque fichier trouvé lors de la marche dans le répertoire.

re.IGNORECASEest facultatif, mais souhaitable sous Windows car le système de fichiers lui-même n'est pas sensible à la casse. (Je n'ai pas pris la peine de compiler l'expression régulière, car les documents indiquent qu'elle doit être mise en cache en interne.)

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
yo-yo
la source
0

J'avais besoin d'une solution pour python 2.x qui fonctionne rapidement sur les grands répertoires.
Je me retrouve avec ceci:

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

Notez que vous pourriez avoir besoin d'une gestion des exceptions au cas où lsil ne trouverait aucun fichier correspondant.

romain
la source
Je viens de réaliser que cela ls src/**/*.cne fonctionne que si l'option globstar est activée ( shopt -s globstar) - voir cette réponse pour plus de détails.
Roman