Wie verwende ich glob (), um Dateien rekursiv zu finden?

738

Das habe ich:

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

aber ich möchte die Unterordner von src durchsuchen. So etwas würde funktionieren:

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

Aber das ist offensichtlich begrenzt und klobig.

Ben Gartner
quelle

Antworten:

1355

Python 3.5+

Da Sie auf einer neuen Python sind, sollten Sie pathlib.Path.rglobaus dem pathlibModul verwenden.

from pathlib import Path

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

Wenn Sie pathlib nicht verwenden möchten, verwenden Sie einfach glob.glob, aber vergessen Sie nicht, den recursiveSchlüsselwortparameter zu übergeben.

Für Fälle, in denen übereinstimmende Dateien mit einem Punkt (.) Beginnen; Verwenden Sie wie Dateien im aktuellen Verzeichnis oder versteckte Dateien auf einem Unix-basierten System dieos.walk Lösung.

Ältere Python-Versionen

Verwenden Sie bei älteren Python-Versionen, um os.walkein Verzeichnis rekursiv zu durchsuchen und fnmatch.filtermit einem einfachen Ausdruck abzugleichen:

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
quelle
3
Für Python älter als 2.2 gibt es os.path.walk()etwas, das etwas umständlicher zu verwenden ist alsos.walk()
John La Rooy
20
@gnibbler Ich weiß, dass dies ein alter Kommentar ist, aber mein Kommentar dient nur dazu, die Leute wissen zu lassen, dass er os.path.walk()veraltet ist und in Python 3 entfernt wurde.
Pedro Cunha
5
@DevC, das in dem in dieser Frage gestellten speziellen Fall möglicherweise funktioniert, aber es ist leicht vorstellbar, dass jemand es mit Abfragen wie 'a * .c' usw. verwenden möchte. Ich denke, es lohnt sich, die aktuelle, etwas langsame Antwort beizubehalten.
Johan Dahlin
2
Für das, was es wert ist, war das Finden von mehr als 10.000 Dateien mit glob viel langsamer als mit os.walk, deshalb habe ich mich aus diesem Grund für die letztere Lösung entschieden.
Godsmith
2
Für Python 3.4 pathlib.Path('src').glob('**/*.c')sollte funktionieren.
CivFan
111

Ähnlich wie bei anderen Lösungen, jedoch mit fnmatch.fnmatch anstelle von glob, da os.walk bereits die Dateinamen aufgelistet hat:

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

Wenn Sie einen Generator verwenden, können Sie jede gefundene Datei so verarbeiten, wie sie gefunden wurde, anstatt alle Dateien zu finden und sie dann zu verarbeiten.

Bruno Oliveira
quelle
3
weil 1-Liner Spaß machen: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

Ich habe das Glob-Modul so geändert, dass es ** für rekursives Globbing unterstützt, z.

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

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

Nützlich, wenn Sie Ihren Benutzern die Möglichkeit geben möchten, die ** -Syntax zu verwenden, und daher ist os.walk () allein nicht gut genug.

miracle2k
quelle
2
Können wir diesen Stopp einlegen, nachdem das erste Spiel gefunden wurde? Vielleicht ist es möglich, es als Generator zu verwenden, anstatt eine Liste aller möglichen Ergebnisse zurückzugeben? Ist dies auch eine DFS oder eine BFS? Ich würde ein BFS sehr bevorzugen, denke ich, damit zuerst Dateien gefunden werden, die sich in der Nähe des Stamms befinden. +1 für die Erstellung dieses Moduls und die Bereitstellung auf GitHub / pip.
ArtOfWarfare
14
Die ** -Syntax wurde dem offiziellen Glob-Modul in Python 3.5 hinzugefügt.
ArtOfWarfare
@ ArtOfWarfare Okay, gut. Dies ist immer noch nützlich für <3.5.
CS95
1
Um rekursives Globbing **mit dem offiziellen Glob-Modul zu aktivieren , gehen Sie wie glob(path, recursive=True)
folgt vor
68

Ab Python 3.4 kann die glob()Methode einer der PathKlassen im neuen Pathlib- Modul verwendet werden, das **Platzhalter unterstützt . Zum Beispiel:

from pathlib import Path

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

Update: Ab Python 3.5 wird dieselbe Syntax auch von unterstützt glob.glob().

Taleinat
quelle
3
In der Tat, und es wird in Python 3.5 sein . Das sollte in Python 3.4 schon so sein, wurde aber versehentlich weggelassen .
Taleinat
Diese Syntax wird jetzt von glob.glob () ab Python 3.5 unterstützt .
Taleinat
Beachten Sie, dass Sie pathlib.PurePath.relative_to auch in Kombination verwenden können, um relative Pfade abzurufen . Siehe meine Antwort hier für mehr Kontext.
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

fnmatch gibt Ihnen genau die gleichen Muster wie glob , also ist dies wirklich ein ausgezeichneter Ersatz für glob.globmit sehr enger Semantik. Eine iterative Version (z. B. ein Generator), für die IOW ein Ersatz ist glob.iglob, ist eine triviale Anpassung (nur yielddie Zwischenergebnisse, anstatt extendeine einzelne Ergebnisliste zu erstellen, die am Ende zurückgegeben wird).

Alex Martelli
quelle
1
Was denkst du über die Verwendung, recursive_glob(pattern, treeroot='.')wie ich sie in meiner Bearbeitung vorgeschlagen habe? Auf diese Weise kann es beispielsweise aufgerufen werden recursive_glob('*.txt')und intuitiv der Syntax von entsprechen glob.
Chris Redford
@ ChrisRedford, ich sehe es so oder so als ein ziemlich kleines Problem. In der jetzigen Form entspricht es der Argumentreihenfolge "files then pattern" von fnmatch.filter, was ungefähr so ​​nützlich ist wie die Möglichkeit, einzelne Argumente abzugleichen glob.glob.
Alex Martelli
25

Für Python> = 3.5 können Sie verwenden **, recursive=True:

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

Demo


Wenn rekursiv ist True, stimmt das Muster ** mit allen Dateien und null oder mehr directoriesund übereinsubdirectories . Wenn dem Muster ein folgt os.sep, werden nur Verzeichnisse und subdirectoriesÜbereinstimmungen angezeigt.

CONvid19
quelle
2
Dies funktioniert besser als pathlib.Path ('./ path /'). Glob (' * / '), weil es auch so in einem Ordner mit der Größe 0 ist
Charles Walker
20

Sie möchten verwenden os.walk, um Dateinamen zu sammeln, die Ihren Kriterien entsprechen. Zum Beispiel:

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

Hier ist eine Lösung mit verschachteltem Listenverständnis os.walkund einfachem Suffixabgleich anstelle von 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')]

Es kann zu einem Einzeiler komprimiert werden:

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

oder verallgemeinert als Funktion:

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

Wenn Sie vollständige globStilmuster benötigen , können Sie dem Beispiel von Alex und Bruno folgen und Folgendes verwenden 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
quelle
7

Vor kurzem musste ich meine Bilder mit der Erweiterung .jpg wiederherstellen. Ich habe photorec ausgeführt und 4579 Verzeichnisse mit 2,2 Millionen Dateien und einer enormen Vielfalt an Erweiterungen wiederhergestellt. Mit dem folgenden Skript konnte ich innerhalb von Minuten 50133 Dateien mit der Erweiterung .jpg auswählen:

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

Überlegen Sie pathlib.rglob().

Dies ist wie ein Anruf Path.glob()mit "**/"vor dem angegebenen relativen Muster hinzugefügt:

import pathlib


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

Siehe auch @ taleinats verwandten Beitrag hier und einen ähnlichen Beitrag an anderer Stelle.

Pylang
quelle
5

Johan und Bruno bieten exzellente Lösungen für die angegebenen Mindestanforderungen. Ich habe gerade freigegeben Formic die Geräte Ant FileSet und Globs , die diese und weitere komplizierte Szenarien umgehen kann. Eine Implementierung Ihrer Anforderung ist:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Andrew Alcock
quelle
1
Formic scheint verlassen zu sein?! Und es unterstützt nicht Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed
5

Basierend auf anderen Antworten ist dies meine aktuelle Arbeitsimplementierung, die verschachtelte XML-Dateien in einem Stammverzeichnis abruft:

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

Ich habe wirklich Spaß mit Python :)

Daveoncode
quelle
3

Eine andere Möglichkeit, dies nur mit dem Glob-Modul zu tun. Setzen Sie einfach die rglob-Methode mit einem Start-Basisverzeichnis und einem passenden Muster, und es wird eine Liste der übereinstimmenden Dateinamen zurückgegeben.

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

Für Python 3.5 und höher

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)

weiter könnten Sie brauchen

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
quelle
3
Ihre erste Codezeile funktioniert nicht zum Durchsuchen von Unterverzeichnissen. Aber wenn Sie es nur erweitern, /**funktioniert es für mich so:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack
2

Oder mit einem Listenverständnis:

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

Gerade gemacht .. es wird Dateien und Verzeichnisse auf hierarchische Weise drucken

Aber ich habe weder Fnmatch noch Walk benutzt

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

Dieser verwendet fnmatch oder regulären Ausdruck:

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

Zusätzlich zu den vorgeschlagenen Antworten können Sie dies mit etwas Magie der faulen Generierung und des Listenverständnisses tun:

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)

Dies passt nicht nur in eine Zeile und vermeidet unnötige Listen im Speicher, sondern hat auch den netten Nebeneffekt, dass Sie es auf ähnliche Weise wie den Operator ** verwenden können, z. B. os.path.join(root, 'some/path/*.c')um alle .c-Dateien in allen zu erhalten Unterverzeichnisse von src, die diese Struktur haben.

f0xdx
quelle
2

Dies ist ein Arbeitscode für Python 2.7. Im Rahmen meiner Devops-Arbeit musste ich ein Skript schreiben, mit dem die mit live-appName.properties gekennzeichneten Konfigurationsdateien nach appName.properties verschoben werden. Es könnte auch andere Erweiterungsdateien wie live-appName.xml geben.

Unten finden Sie einen Arbeitscode dafür, der die Dateien in den angegebenen Verzeichnissen (verschachtelte Ebene) findet und sie dann in den erforderlichen Dateinamen umbenennt (verschiebt)

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)

Diese Funktion wird von einem Hauptskript aus aufgerufen

flipProperties(searchDir)

Ich hoffe, dies hilft jemandem, der mit ähnlichen Problemen zu kämpfen hat.

Sanjay Bharwani
quelle
1

Vereinfachte Version von Johan Dahlins Antwort ohne 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']
flussfrei
quelle
1

Hier ist meine Lösung, die das Listenverständnis verwendet, um rekursiv in einem Verzeichnis und allen Unterverzeichnissen nach mehreren Dateierweiterungen zu suchen :

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
Sackkraft
quelle
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
quelle
0

Ich habe die Top-Antwort in diesem Beitrag geändert. Und kürzlich dieses Skript erstellt, das alle Dateien in einem bestimmten Verzeichnis (Suchverzeichnis) und den Unterverzeichnissen darunter durchläuft ... und Dateiname, Stammverzeichnis, Änderungs- / Erstellungsdatum und Größe.

Hoffe das hilft jemandem ... und er kann das Verzeichnis durchgehen und fileinfo bekommen.

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

Hier ist eine Lösung, die das Muster mit dem vollständigen Pfad und nicht nur mit dem Basisdateinamen vergleicht.

Es verwendet fnmatch.translate , um ein Muster im Glob-Stil in einen regulären Ausdruck zu konvertieren, der dann mit dem vollständigen Pfad jeder Datei verglichen wird, die beim Durchsuchen des Verzeichnisses gefunden wurde.

re.IGNORECASEist optional, aber unter Windows wünschenswert, da das Dateisystem selbst nicht zwischen Groß- und Kleinschreibung unterscheidet. (Ich habe mich nicht darum gekümmert, den regulären Ausdruck zu kompilieren, da Dokumente angeben, dass er intern zwischengespeichert werden sollte.)

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

Ich brauchte eine Lösung für Python 2.x , die in großen Verzeichnissen schnell funktioniert .
Ich beende damit:

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

Beachten Sie, dass Sie möglicherweise eine Ausnahmebehandlung benötigen, falls lskeine passende Datei gefunden wird.

römisch
quelle
Ich habe gerade festgestellt, dass dies ls src/**/*.cnur funktioniert, wenn die Globstar-Option aktiviert ist ( shopt -s globstar). Weitere Informationen finden Sie in dieser Antwort .
Roman