Bagaimana cara menggunakan glob () untuk mencari file secara rekursif?

738

Inilah yang saya miliki:

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

tapi saya ingin mencari subfolder dari src. Sesuatu seperti ini akan berhasil:

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

Tapi ini jelas terbatas dan kikuk.

Ben Gartner
sumber

Jawaban:

1355

Python 3.5+

Karena Anda menggunakan python baru, Anda harus menggunakan pathlib.Path.rglobdari pathlibmodul.

from pathlib import Path

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

Jika Anda tidak ingin menggunakan pathlib, gunakan saja glob.glob, tetapi jangan lupa untuk memasukkan recursiveparameter kata kunci.

Untuk kasus di mana file yang cocok dimulai dengan titik (.); seperti file di direktori saat ini atau file tersembunyi di sistem berbasis Unix, gunakan os.walksolusi di bawah ini.

Versi Python yang lebih lama

Untuk versi Python yang lebih lama, gunakan os.walkuntuk menjalankan direktori secara rekursif dan fnmatch.filtermencocokkan dengan ekspresi sederhana:

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
sumber
3
Untuk Python lebih tua dari 2.2 ada os.path.walk()yang sedikit lebih fiddly untuk digunakan daripadaos.walk()
John La Rooy
20
@gnibbler Saya tahu itu adalah komentar lama, tapi komentar saya hanya untuk membuat orang tahu bahwa os.path.walk()itu sudah usang dan telah dihapus dengan Python 3.
Pedro Cunha
5
@ DevC yang mungkin berfungsi dalam kasus spesifik yang ditanyakan dalam pertanyaan ini, tetapi mudah untuk membayangkan seseorang yang ingin menggunakannya dengan pertanyaan seperti 'a * .c' dll, jadi saya pikir ada baiknya menjaga jawaban yang agak lambat saat ini.
Johan Dahlin
2
Untuk apa nilainya, dalam kasus saya menemukan 10.000 file dengan glob jauh lebih lambat daripada dengan os.walk, jadi saya pergi dengan solusi yang terakhir karena alasan itu.
Godsmith
2
Untuk python 3.4, pathlib.Path('src').glob('**/*.c')harus berfungsi.
CivFan
111

Mirip dengan solusi lain, tetapi menggunakan fnmatch.fnmatch bukan glob, karena os.walk sudah mendaftarkan nama 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

Juga, menggunakan generator memungkinkan Anda untuk memproses setiap file seperti yang ditemukan, alih-alih menemukan semua file dan kemudian memprosesnya.

Bruno Oliveira
sumber
3
karena 1-liner itu menyenangkan: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

Saya telah memodifikasi modul glob untuk mendukung ** untuk globbing rekursif, misalnya:

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

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

Berguna saat Anda ingin memberi pengguna Anda kemampuan untuk menggunakan sintaks **, dan dengan demikian os.walk () saja tidak cukup baik.

miracle2k
sumber
2
Bisakah kita menghentikan ini setelah menemukan kecocokan pertama? Mungkin memungkinkan untuk menggunakannya sebagai generator alih-alih mengembalikan daftar setiap hasil yang mungkin? Juga, apakah ini DFS atau BFS? Saya lebih suka BFS, saya pikir, sehingga file yang dekat root ditemukan terlebih dahulu. +1 untuk membuat modul ini dan menyediakannya di GitHub / pip.
ArtOfWarfare
14
Sintaks ** ditambahkan ke modul glob resmi dalam Python 3.5.
ArtOfWarfare
@ ArtOfWarfare Baiklah, baiklah. Ini masih berguna untuk <3.5.
cs95
1
Untuk mengaktifkan globbing rekursif menggunakan **modul glob resmi, lakukan:glob(path, recursive=True)
winklerrr
68

Dimulai dengan Python 3.4, seseorang dapat menggunakan glob()metode salah satu Pathkelas dalam modul pathlib baru , yang mendukung **wildcard. Sebagai contoh:

from pathlib import Path

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

Pembaruan: Dimulai dengan Python 3.5, sintaks yang sama juga didukung oleh glob.glob().

taleinat
sumber
3
Memang, dan itu akan berada di Python 3.5 . Seharusnya sudah seperti itu di Python 3.4, tetapi dihilangkan karena kesalahan .
taleinat
Sintaks ini sekarang didukung oleh glob.glob () pada Python 3.5 .
taleinat
Perhatikan bahwa Anda juga dapat menggunakan pathlib.PurePath.relative_to dalam kombinasi untuk mendapatkan jalur relatif. Lihat jawaban saya di sini untuk lebih banyak konteks.
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

fnmatchmemberi Anda pola yang persis sama glob, jadi ini benar-benar pengganti yang sangat baik untuk glob.globsemantik yang sangat dekat. Versi berulang (misalnya generator), IOW pengganti glob.iglob, adalah adaptasi sepele (hanya yieldhasil antara saat Anda pergi, alih-alih extenddaftar hasil tunggal untuk kembali di akhir).

Alex Martelli
sumber
1
Apa yang Anda pikirkan tentang penggunaan recursive_glob(pattern, treeroot='.')seperti yang saya sarankan di edit saya? Dengan cara ini, dapat disebut misalnya recursive_glob('*.txt')dan secara intuitif cocok dengan sintaksis glob.
Chris Redford
@ ChrisRedford, saya melihatnya sebagai masalah yang cukup kecil. Seperti yang ada sekarang, ini cocok dengan urutan argumen "file lalu pola" fnmatch.filter, yang kira-kira sama bermanfaatnya dengan kemungkinan mencocokkan argumen tunggal glob.glob.
Alex Martelli
25

Untuk python> = 3,5 Anda dapat menggunakan **, recursive=True:

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

Demo


Jika rekursif adalah True , polanya ** akan cocok dengan semua file dan nol atau lebih directoriesdansubdirectories . Jika polanya diikuti oleh os.sep, hanya direktori dan yang subdirectoriescocok.

CONvid19
sumber
2
Ini berfungsi lebih baik daripada pathlib.Path ('./ path /'). Glob (' * / ') karena juga demikian dalam folder dengan ukuran 0
Charles Walker
20

Anda ingin menggunakan os.walkuntuk mengumpulkan nama file yang sesuai dengan kriteria Anda. Sebagai contoh:

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

Berikut ini solusi dengan pemahaman daftar bersarang, os.walkdan pencocokan akhiran sederhana alih-alih 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')]

Itu dapat dikompres menjadi satu-liner:

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

atau digeneralisasi sebagai fungsi:

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

Jika Anda membutuhkan globpola gaya penuh , Anda dapat mengikuti contoh Alex dan Bruno dan menggunakan 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
sumber
7

Baru-baru ini saya harus memulihkan gambar saya dengan ekstensi .jpg. Saya menjalankan photorec dan memulihkan 4579 direktori dalam 2,2 juta file di dalamnya, memiliki beragam ekstensi yang luar biasa. Dengan skrip di bawah ini saya dapat memilih 50133 file dengan ekstensi .jpg dalam beberapa menit:

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

Pertimbangkan pathlib.rglob().

Ini seperti menelepon Path.glob()dengan "**/"menambahkan di depan pola relatif yang diberikan:

import pathlib


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

Lihat juga pos terkait @ taleinat di sini dan pos serupa di tempat lain.

pylang
sumber
5

Johan dan Bruno memberikan solusi luar biasa pada persyaratan minimal seperti yang dinyatakan. Saya baru saja merilis Formic yang mengimplementasikan Ant FileSet dan Glob yang dapat menangani ini dan skenario yang lebih rumit. Implementasi dari kebutuhan Anda adalah:

import formic
fileset = formic.FileSet(include="/src/**/*.c")
for file_name in fileset.qualified_files():
    print file_name
Andrew Alcock
sumber
1
Formik tampaknya ditinggalkan ?! Dan itu tidak mendukung Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed
5

berdasarkan jawaban lain ini adalah implementasi kerja saya saat ini, yang mengambil file xml bersarang di direktori root:

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

Saya benar-benar bersenang-senang dengan python :)

daveoncode
sumber
3

Cara lain untuk melakukannya hanya dengan menggunakan modul glob. Cukup seed metode rglob dengan direktori basis awal dan pola untuk mencocokkan dan itu akan mengembalikan daftar nama file yang cocok.

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

Untuk python 3.5 dan yang lebih baru

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)

lebih lanjut Anda mungkin perlu

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
sumber
3
Baris kode pertama Anda tidak berfungsi untuk melihat ke subdirektori. Tetapi jika Anda hanya mengembangkannya dengan cara /**itu bekerja untuk saya, seperti itu:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack
2

Atau dengan pemahaman daftar:

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

Baru saja membuat ini .. itu akan mencetak file dan direktori dengan cara hierarkis

Tapi saya tidak menggunakan fnmatch atau berjalan

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

Yang itu menggunakan fnmatch atau ekspresi reguler:

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

Selain jawaban yang disarankan, Anda dapat melakukan ini dengan beberapa generasi malas dan sihir pemahaman daftar:

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)

Selain pas dalam satu baris dan menghindari daftar yang tidak perlu dalam memori, ini juga memiliki efek samping yang bagus, yang dapat Anda gunakan dengan cara yang mirip dengan operator **, misalnya, Anda dapat menggunakan os.path.join(root, 'some/path/*.c')untuk mendapatkan semua file .c di semua sub direktori src yang memiliki struktur ini.

f0xdx
sumber
2

Ini adalah kode yang berfungsi pada Python 2.7. Sebagai bagian dari pekerjaan devops saya, saya diminta untuk menulis skrip yang akan memindahkan file konfigurasi yang ditandai dengan live-appName.properties ke appName.properties. Mungkin ada file ekstensi lain juga seperti live-appName.xml.

Di bawah ini adalah kode yang berfungsi untuk ini, yang menemukan file dalam direktori yang diberikan (level bersarang) dan kemudian mengganti nama (memindahkan) file tersebut ke nama file yang diperlukan

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)

Fungsi ini dipanggil dari skrip utama

flipProperties(searchDir)

Semoga ini bisa membantu seseorang yang berjuang dengan masalah serupa.

Sanjay Bharwani
sumber
1

Versi sederhana dari jawaban Johan Dahlin, tanpa 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']
mengalir bebas
sumber
1

Berikut ini adalah solusi saya menggunakan pemahaman daftar untuk mencari beberapa ekstensi file secara rekursif dalam direktori dan semua subdirektori:

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
kekuatan karung
sumber
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
sumber
0

Saya mengubah jawaban teratas dalam posting ini .. dan baru-baru ini membuat skrip ini yang akan mengulang semua file dalam direktori tertentu (searchdir) dan sub-direktori di bawahnya ... dan mencetak nama file, rootdir, tanggal modifikasi / pembuatan, dan ukuran.

Semoga ini bisa membantu seseorang ... dan mereka bisa menjalankan direktori dan mendapatkan 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
sumber
0

Berikut adalah solusi yang akan cocok dengan pola terhadap path lengkap dan bukan hanya nama file dasar.

Itu menggunakan fnmatch.translate untuk mengubah pola gabus menjadi ekspresi reguler, yang kemudian dicocokkan dengan path lengkap setiap file yang ditemukan saat berjalan direktori.

re.IGNORECASEbersifat opsional, tetapi diinginkan pada Windows karena sistem file itu sendiri tidak peka terhadap huruf besar-kecil. (Saya tidak repot-repot menyusun regex karena dokumen menunjukkan itu harus di-cache secara internal.)

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

Saya membutuhkan solusi untuk python 2.x yang bekerja cepat di direktori besar.
Saya berakhir dengan ini:

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

Perhatikan bahwa Anda mungkin memerlukan penanganan pengecualian jika lstidak menemukan file yang cocok.

Roma
sumber
Saya baru menyadari bahwa ini ls src/**/*.chanya berfungsi jika opsi globstar diaktifkan ( shopt -s globstar) - lihat jawaban ini untuk detailnya.
Roman