Jak używać glob () do rekurencyjnego wyszukiwania plików?


738

Oto co mam:

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

ale chcę przeszukać podfoldery src. Coś takiego działałoby:

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

Ale jest to oczywiście ograniczone i niezgrabne.

Odpowiedzi:


1355

Python 3.5+

Ponieważ korzystasz z nowego Pythona, powinieneś używać pathlib.Path.rglobz pathlibmodułu.

from pathlib import Path

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

Jeśli nie chcesz używać pathlib, po prostu użyj glob.glob, ale nie zapomnij podać recursiveparametru słowa kluczowego.

W przypadkach, gdy pasujące pliki zaczynają się od kropki (.); jak pliki w bieżącym katalogu lub pliki ukryte w systemie uniksowym, skorzystaj z os.walkponiższego rozwiązania.

Starsze wersje Pythona

W przypadku starszych wersji Python użyj os.walkrekurencyjnie chodzić po katalogu i fnmatch.filterdopasować do prostego wyrażenia:

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
Dla Pythona starszego niż 2.2 jest os.path.walk()nieco bardziej kłopotliwy w użyciu niżos.walk()
John La Rooy

20
@gnibbler Wiem, że to stary komentarz, ale mój komentarz ma na celu poinformowanie ludzi, że os.path.walk()jest on przestarzały i został usunięty w Pythonie 3.
Pedro Cunha

5
@DevC, które może działać w konkretnym przypadku zadawanym w tym pytaniu, ale łatwo jest wyobrazić sobie kogoś, kto chce to zrobić z zapytaniami takimi jak „a * .c” itp., Więc myślę, że warto zachować obecną nieco powolną odpowiedź.
Johan Dahlin

2
Za to, co jest warte, w moim przypadku znalezienie ponad 10 000 plików z glob było znacznie wolniejsze niż z os.walk, dlatego z tego powodu wybrałem to drugie rozwiązanie.
Godsmith

2
W przypadku python 3.4 pathlib.Path('src').glob('**/*.c')powinien działać.
CivFan

111

Podobne do innych rozwiązań, ale z użyciem fnmatch.fnmatch zamiast glob, ponieważ os.walk już podał nazwy plików:

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

Ponadto użycie generatora pozwala przetwarzać każdy znaleziony plik zamiast znajdować wszystkie pliki, a następnie je przetwarzać.


3
ponieważ 1-liniowce są fajne: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

Zmodyfikowałem moduł glob, aby obsługiwał ** dla globowania rekurencyjnego, np .:

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

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

Przydatne, gdy chcesz zapewnić użytkownikom możliwość korzystania ze składni **, a zatem sama os.walk () nie jest wystarczająco dobra.


2
Czy możemy to zatrzymać po znalezieniu pierwszego dopasowania? Może sprawi, że będzie można go używać jako generatora zamiast zwracania listy wszystkich możliwych wyników? Czy to także DFS czy BFS? Myślę, że zdecydowanie wolałbym BFS, aby najpierw znaleźć pliki znajdujące się w pobliżu katalogu głównego. +1 za zrobienie tego modułu i udostępnienie go na GitHub / pip.
ArtOfWarfare

14
Składnia ** została dodana do oficjalnego modułu glob w Pythonie 3.5.
ArtOfWarfare

@ArtOfWarfare W porządku, w porządku. Jest to nadal przydatne dla <3.5.
cs95,

1
Aby aktywować globowanie rekurencyjne za **pomocą oficjalnego modułu glob, wykonaj:glob(path, recursive=True)
winklerrr

68

Począwszy od Pythona 3.4, można użyć glob()metody jednej z Pathklas w nowym module pathlib , który obsługuje **symbole wieloznaczne. Na przykład:

from pathlib import Path

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

Aktualizacja: Począwszy od Python 3.5, ta sama składnia jest również obsługiwana przez glob.glob().


3
Rzeczywiście, i będzie w Python 3.5 . Tak miało być już w Pythonie 3.4, ale zostało omyłkowo pominięte .
taleinat


Zauważ, że możesz również użyć pathlib.PurePath.relative_to w kombinacji, aby uzyskać ścieżki względne. Zobacz moją odpowiedź tutaj, aby uzyskać więcej kontekstu.
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

fnmatchdaje dokładnie takie same wzory jak glob, więc jest to naprawdę doskonały zamiennik dla glob.globbardzo ścisłej semantyki. Wersja iteracyjna (np. Generator), zastępująca IOW glob.iglob, jest trywialną adaptacją (tylko yieldpośrednie wyniki na bieżąco, zamiast extendwprowadzania pojedynczej listy wyników do powrotu na końcu).


1
Co sądzisz o używaniu, recursive_glob(pattern, treeroot='.')jak zasugerowałem w mojej edycji? W ten sposób można go na przykład wywołać recursive_glob('*.txt')i intuicyjnie dopasować składnię glob.
Chris Redford,

@ChrisRedford, i tak uważam to za dość drobny problem. W obecnej formie dopasowuje kolejność argumentów „pliki a następnie wzorzec” fnmatch.filter, co jest mniej więcej tak przydatne, jak możliwość dopasowania pojedynczego argumentu glob.glob.
Alex Martelli,

25

Dla python> = 3,5 można użyć **, recursive=True:

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

Próbny


Jeśli jest rekurencyjny True, wzorzec ** będzie pasował do wszystkich plików i zero lub więcej directoriesisubdirectories . Jeśli po wzorcu następuje os.sep, tylko katalogi i subdirectoriespasują.


2
Działa to lepiej niż pathlib.Path ('./ path /'). Glob (' * / '), ponieważ działa również w folderze o rozmiarze 0
Charles Walker

20

Będziesz chciał użyć os.walkdo zbierania nazw plików, które spełniają Twoje kryteria. Na przykład:

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

Oto rozwiązanie z listami zagnieżdżonymi os.walki prostym dopasowaniem sufiksu zamiast 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')]

Można go skompresować do jednej linijki:

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

lub uogólnione jako funkcja:

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

Jeśli potrzebujesz pełnych globwzorów, możesz pójść za przykładem Alexa i Bruno i użyć 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

Ostatnio musiałem odzyskać moje zdjęcia z rozszerzeniem .jpg. Uruchomiłem photorec i odzyskałem 4579 katalogów 2,2 miliona plików, mając ogromną różnorodność rozszerzeń. Za pomocą poniższego skryptu byłem w stanie wybrać 50133 plików z rozszerzeniem .jpg w ciągu kilku minut:

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

Zastanów się pathlib.rglob().

To jest jak wywołanie Path.glob()z "**/"dodanym przed podanym wzorcem względnym:

import pathlib


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

Zobacz także powiązany post @ taleinat tutaj i podobny post gdzie indziej.


5

Johan i Bruno zapewniają doskonałe rozwiązania dotyczące minimalnych wymagań, jak podano. Właśnie wydałem Formic, który implementuje Ant FileSet i Globs, które mogą obsłużyć ten i bardziej skomplikowane scenariusze. Realizacja twojego wymagania to:

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

1
Formic wydaje się być porzucony ?! I nie obsługuje Pythona 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed

5

w oparciu o inne odpowiedzi jest to moja bieżąca działająca implementacja, która pobiera zagnieżdżone pliki xml w katalogu głównym:

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

Naprawdę dobrze się bawię z Pythonem :)


3

Kolejny sposób, aby to zrobić za pomocą samego modułu glob. Po prostu zapisz metodę rglob początkowym katalogiem podstawowym i pasującym wzorcem, a ona zwróci listę pasujących nazw plików.

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

Dla python 3.5 i nowszych

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)

dalej możesz potrzebować

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
Twój pierwszy wiersz kodu nie działa w przypadku wyszukiwania w podkatalogach. Ale jeśli tylko go rozszerzysz, /**działa dla mnie w ten sposób:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack

2

Lub ze zrozumieniem listy:

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

Właśnie to zrobiłem .. wydrukuje pliki i katalog w sposób hierarchiczny

Ale nie użyłem fnmatcha ani chodzenia

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

Ten używa fnmatch lub wyrażenia regularnego:

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

Oprócz sugerowanych odpowiedzi możesz to zrobić za pomocą leniwej generacji i magii ze zrozumieniem listy:

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)

Poza dopasowaniem do jednej linii i unikaniem niepotrzebnych list w pamięci, ma to również fajny efekt uboczny, że możesz go użyć w sposób podobny do operatora **, np. Możesz użyć os.path.join(root, 'some/path/*.c'), aby uzyskać wszystkie pliki .c we wszystkich podkatalogi src, które mają tę strukturę.


2

To jest działający kod w Pythonie 2.7. W ramach mojej pracy devops musiałem napisać skrypt, który przeniósłby pliki konfiguracyjne oznaczone live-appName.properties do appName.properties. Mogą istnieć inne pliki rozszerzeń, takie jak live-appName.xml.

Poniżej znajduje się działający kod, który znajduje pliki w podanych katalogach (poziom zagnieżdżony), a następnie zmienia nazwy (przenosi) na wymaganą nazwę pliku

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)

Ta funkcja jest wywoływana ze skryptu głównego

flipProperties(searchDir)

Mam nadzieję, że to pomoże komuś zmagać się z podobnymi problemami.


1

Uproszczona wersja odpowiedzi Johana Dahlina, bez fnmatcha .

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

Oto moje rozwiązanie wykorzystujące funkcję list list do wyszukiwania wielu rozszerzeń plików rekurencyjnie w katalogu i wszystkich podkatalogach:

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

Zmodyfikowałem najwyższą odpowiedź w tym poście .. i niedawno utworzyłem ten skrypt, który będzie przechodził przez wszystkie pliki w danym katalogu (searchdir) i podkatalogach pod nim ... i drukuje nazwę pliku, katalog główny, datę modyfikacji / utworzenia i rozmiar.

Mam nadzieję, że to pomoże komuś ... i może przejść do katalogu i uzyskać 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

Oto rozwiązanie, które dopasuje wzór do pełnej ścieżki, a nie tylko podstawowej nazwy pliku.

To używa fnmatch.translate do konwertowania wzorca globalnego na wyrażenie regularne, które jest następnie porównywane z pełną ścieżką każdego pliku znalezionego podczas przechodzenia przez katalog.

re.IGNORECASEjest opcjonalny, ale pożądany w systemie Windows, ponieważ sam system plików nie rozróżnia wielkości liter. (Nie zawracałem sobie głowy kompilowaniem wyrażenia regularnego, ponieważ dokumenty wskazują, że powinien on być buforowany wewnętrznie).

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

Potrzebowałem rozwiązania dla Pythona 2.x, które działa szybko w dużych katalogach.
Kończę z tym:

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

Pamiętaj, że możesz potrzebować obsługi wyjątków na wypadek, gdyby lsnie znalazł pasującego pliku.


Właśnie zdałem sobie sprawę, że ls src/**/*.cdziała tylko wtedy, gdy włączona jest opcja globstar ( shopt -s globstar) - szczegółowe informacje znajdują się w tej odpowiedzi .
Roman
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.