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.

Ben Gartner
źródło

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))
Johan Dahlin
źródło
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ć.

Bruno Oliveira
źródło
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.

miracle2k
źródło
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().

taleinat
źródło
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
Ta składnia jest teraz obsługiwana przez glob.glob () od Python 3.5 .
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).

Alex Martelli
źródło
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ą.

CONvid19
źródło
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))
Geoff Reedy
źródło
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')
akaihola
źródło
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)
Mustafa inetyna
źródło
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.

pylang
źródło
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
Andrew Alcock
źródło
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 :)

daveoncode
źródło
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
chris-piekarski
źródło
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'
Sami
źródło
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") ] 
xtofl
źródło
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)
Shaurya Gupta
źródło
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')])
hipertracker
źródło
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ę.

f0xdx
źródło
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.

Sanjay Bharwani
źródło
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']
bez przepływu
źródło
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
sackpower
źródło
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
źródło
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))
wysokość
źródło
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
yoyo
źródło
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.

rzymski
źródło
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