วิธีใช้ glob () เพื่อค้นหาไฟล์ซ้ำ ๆ


738

นี่คือสิ่งที่ฉันมี:

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

แต่ฉันต้องการค้นหาโฟลเดอร์ย่อยของ src สิ่งนี้จะทำงาน:

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

แต่เห็นได้ชัดว่ามีข้อ จำกัด และ clunky

คำตอบ:


1355

Python 3.5+

เนื่องจากคุณอยู่บนงูหลามใหม่คุณควรใช้pathlib.Path.rglobจากpathlibโมดูล

from pathlib import Path

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

หากคุณไม่ต้องการใช้ pathlib ให้ใช้glob.globแต่อย่าลืมส่งผ่านrecursiveพารามิเตอร์คำหลัก

สำหรับกรณีที่ไฟล์ที่ตรงกันเริ่มต้นด้วยจุด (.); เช่นไฟล์ในไดเรกทอรีปัจจุบันหรือไฟล์ที่ซ่อนอยู่ในระบบที่ใช้ Unix ใช้os.walkวิธีแก้ปัญหาด้านล่าง

งูหลามรุ่นเก่า

สำหรับเวอร์ชัน Python ที่เก่ากว่าใช้os.walkเพื่อเดินไดเรกทอรีซ้ำและfnmatch.filterจับคู่กับนิพจน์ทั่วไป:

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
สำหรับ Python ที่มีอายุมากกว่า 2.2 จะos.path.walk()มีข้อผิดพลาดเล็กน้อยที่จะใช้มากกว่าos.walk()
John La Rooy

20
@gnibbler ฉันรู้ว่ามันเป็นความคิดเห็นเก่า แต่ความคิดเห็นของฉันเป็นเพียงเพื่อให้คนรู้ว่าos.path.walk()เลิกใช้แล้วและถูกลบใน Python 3
Pedro Cunha

5
@DevC ที่อาจทำงานในกรณีเฉพาะที่ถามในคำถามนี้ แต่มันง่ายที่จะจินตนาการถึงคนที่ต้องการจะใช้กับแบบสอบถามเช่น 'a * .c' ฯลฯ ดังนั้นฉันคิดว่ามันคุ้มค่าที่จะรักษาคำตอบที่ค่อนข้างช้าในปัจจุบัน
Johan Dahlin

2
สำหรับสิ่งที่คุ้มค่าในกรณีของฉันการค้นหาไฟล์มากกว่า 10,000 ไฟล์ที่มี glob นั้นช้ากว่าการใช้ os.walk มากดังนั้นฉันจึงไปหาคำตอบหลังด้วยเหตุผลนั้น
Godsmith

2
สำหรับ python 3.4 pathlib.Path('src').glob('**/*.c')ควรใช้งานได้
CivFan

111

คล้ายกับโซลูชันอื่น ๆ แต่ใช้ fnmatch.fnmatch แทน glob เนื่องจาก os.walk มีชื่อไฟล์อยู่แล้ว:

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

นอกจากนี้การใช้ตัวสร้างจะให้คุณประมวลผลแต่ละไฟล์ตามที่พบแทนที่จะค้นหาไฟล์ทั้งหมดแล้วทำการประมวลผล


3
เพราะ 1-liners สนุก: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

ฉันได้แก้ไขโมดูล glob เพื่อสนับสนุน ** สำหรับการวนซ้ำแบบวนซ้ำเช่น:

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

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

มีประโยชน์เมื่อคุณต้องการให้ผู้ใช้ของคุณมีความสามารถในการใช้ไวยากรณ์ ** และ os.walk () เพียงอย่างเดียวนั้นไม่ดีพอ


2
เราสามารถหยุดนี้หลังจากพบคู่แรกได้หรือไม่? อาจทำให้มันเป็นไปได้ที่จะใช้มันเป็นเครื่องกำเนิดไฟฟ้าแทนที่จะส่งกลับรายการผลลัพธ์ที่เป็นไปได้ทุกครั้งใช่หรือไม่ นอกจากนี้ยังเป็น DFS หรือ BFS หรือไม่ ฉันชอบ BFS มากฉันคิดว่าดังนั้นไฟล์ที่อยู่ใกล้รูทจะพบก่อน +1 สำหรับการสร้างโมดูลนี้และให้ไว้ใน GitHub / pip
ArtOfWarfare

14
เพิ่มไวยากรณ์ ** ในโมดูล glob อย่างเป็นทางการใน Python 3.5
ArtOfWarfare

@ArtOfWarfare เอาล่ะก็ได้ สิ่งนี้ยังคงมีประโยชน์สำหรับ <3.5
cs95

1
ในการเปิดใช้งานการวนซ้ำแบบซ้ำโดยใช้**กับโมดูล glob อย่างเป็นทางการให้ทำ:glob(path, recursive=True)
winklerrr

68

เริ่มต้นด้วย Python 3.4 สามารถใช้glob()วิธีการหนึ่งในPathคลาสในโมดูลpathlibใหม่ซึ่งรองรับ**wildcard ตัวอย่างเช่น:

from pathlib import Path

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

ปรับปรุง: เริ่มต้นกับงูหลาม 3.5 glob.glob()ไวยากรณ์เดียวกันยังได้รับการสนับสนุนโดย


3
แน่นอนและมันจะอยู่ใน Python 3.5 มันควรจะเป็นอย่างนั้นอยู่แล้วในหลาม 3.4 แต่ถูกมองข้ามโดยไม่ได้ตั้งใจ
taleinat


โปรดทราบว่าคุณยังสามารถใช้pathlib.PurePath.relative_toร่วมกันเพื่อรับพา ธ สัมพัทธ์ ดูคำตอบของฉันที่นี่สำหรับบริบทเพิ่มเติม
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ให้รูปแบบเดียวกับglobคุณดังนั้นนี่จึงเป็นสิ่งทดแทนที่ยอดเยี่ยมสำหรับglob.globความหมายที่ใกล้เคียงกันมาก เวอร์ชันซ้ำ (เช่นตัวสร้าง) IOW การแทนที่glob.iglobคือการปรับตัวเล็กน้อย (เพียงyieldผลลัพธ์ระดับกลางในขณะที่คุณไปแทนที่จะextendเป็นรายการผลลัพธ์เดียวที่จะกลับมาที่จุดสิ้นสุด)


1
คุณคิดอย่างไรเกี่ยวกับการใช้งานrecursive_glob(pattern, treeroot='.')ตามที่ฉันแนะนำในการแก้ไขของฉัน วิธีนี้จะสามารถเรียกว่าเป็นตัวอย่างและสังหรณ์ใจตรงกับไวยากรณ์ของrecursive_glob('*.txt') glob
Chris Redford

@ ChrisRedford ฉันเห็นว่ามันเป็นปัญหาเล็กน้อยที่น่าสนใจไม่ทางใดก็ทางหนึ่ง ขณะที่มันยืนตอนนี้มันตรงกับ "ไฟล์แล้วรูปแบบ" สั่งข้อโต้แย้งของซึ่งเป็นประมาณประโยชน์เท่าที่เป็นไปได้ของการจับคู่เดียวอาร์กิวเมนต์fnmatch.filter glob.glob
Alex Martelli

25

สำหรับหลาม> = 3.5คุณสามารถใช้**, recursive=True:

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

การสาธิต


หาก recursive เป็นTrueรูปแบบ** จะตรงกับไฟล์ใด ๆ และเป็นศูนย์หรือมากกว่าdirectoriessubdirectoriesและ หากรูปแบบตามด้วยos.sepไดเรกทอรีและการsubdirectoriesจับคู่เท่านั้น


2
สิ่งนี้ทำงานได้ดีกว่า pathlib.Path ('./ path /'). glob (' * / ') เพราะมันยังอยู่ในโฟลเดอร์ที่มีขนาดเท่ากับ 0
Charles Walker

20

คุณจะต้องใช้os.walkเพื่อรวบรวมชื่อไฟล์ที่ตรงกับเกณฑ์ของคุณ ตัวอย่างเช่น:

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

ต่อไปนี้เป็นคำตอบของความเข้าใจในรายการแบบซ้อนos.walkและการจับคู่คำต่อท้ายแบบง่ายแทน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')]

มันสามารถบีบอัดให้เป็นหนึ่งซับ:

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

หรือทำเป็นฟังก์ชั่นทั่วไป:

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

หากคุณต้องการรูปglobแบบเต็มคุณสามารถทำตามตัวอย่างของ Alex และ Bruno และใช้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

เมื่อเร็ว ๆ นี้ฉันต้องกู้ภาพของฉันด้วยนามสกุล. jpg ฉันวิ่ง photorec และกู้คืน 4579 ไดเรกทอรี 2.2 ล้านไฟล์ภายในมีนามสกุลต่าง ๆ มากมายด้วยสคริปต์ด้านล่างฉันสามารถเลือกไฟล์ 50133 havin .jpg นามสกุลภายในไม่กี่นาที:

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

pathlib.rglob()พิจารณา

นี่เป็นเหมือนการโทรPath.glob()โดย"**/"เพิ่มไว้ด้านหน้ารูปแบบที่สัมพันธ์กัน:

import pathlib


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

ดูโพสต์ที่เกี่ยวข้องของ @ taleinat ที่นี่และโพสต์อื่นที่คล้ายกัน


5

Johan และ Bruno มอบโซลูชั่นที่ยอดเยี่ยมสำหรับความต้องการขั้นต่ำตามที่ระบุ ฉันเพิ่งเปิดตัวFormicซึ่งใช้ Ant FileSet และ Globsซึ่งสามารถจัดการกับสถานการณ์นี้และซับซ้อนกว่าได้ การดำเนินการตามความต้องการของคุณคือ:

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

1
Formic ดูเหมือนจะถูกทิ้งร้าง! และไม่รองรับ Python 3 ( bitbucket.org/aviser/formic/issue/12/support-python-3 )
blueyed

5

ตามคำตอบอื่น ๆ นี่คือการใช้งานในปัจจุบันของฉันซึ่งดึงไฟล์ xml ที่ซ้อนกันในไดเรกทอรีราก:

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

ฉันสนุกกับงูหลามจริงๆ :)


3

อีกวิธีในการทำโดยใช้โมดูล glob เพียงเมล็ดวิธี rglob กับไดเรกทอรีฐานเริ่มต้นและรูปแบบเพื่อให้ตรงกับและมันจะกลับรายการของชื่อไฟล์ที่ตรงกัน

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

สำหรับ python 3.5 และใหม่กว่า

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)

ต่อไปคุณอาจต้อง

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
รหัสบรรทัดแรกของคุณใช้ไม่ได้กับการค้นหาไดเรกทอรีย่อย แต่ถ้าคุณเพิ่งขยายมัน/**มันใช้งานได้สำหรับฉันแบบนั้น:file_names_array = glob.glob('src/**/*.c', recursive=True)
NeStack

2

หรือด้วยความเข้าใจในรายการ:

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

เพิ่งทำสิ่งนี้ .. มันจะพิมพ์ไฟล์และไดเรกทอรีตามลำดับชั้น

แต่ฉันไม่ได้ใช้ fnmatch หรือเดิน

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

สิ่งนั้นใช้ fnmatch หรือนิพจน์ทั่วไป:

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

นอกเหนือจากคำตอบที่แนะนำคุณสามารถทำสิ่งนี้กับคนขี้เกียจรุ่นและรายการความเข้าใจ:

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)

นอกจากการติดตั้งในบรรทัดเดียวและหลีกเลี่ยงรายการที่ไม่จำเป็นในหน่วยความจำแล้วยังมีผลข้างเคียงที่ดีที่คุณสามารถใช้ในลักษณะที่คล้ายกับตัวดำเนินการ ** เช่นคุณสามารถใช้os.path.join(root, 'some/path/*.c')เพื่อรับไฟล์. c ทั้งหมด ไดเร็กทอรีย่อยของ src ที่มีโครงสร้างนี้


2

นี่เป็นรหัสที่ใช้งานได้บน Python 2.7 ในฐานะที่เป็นส่วนหนึ่งของงาน devops ของฉันฉันต้องเขียนสคริปต์ซึ่งจะย้ายไฟล์ปรับแต่งที่มีเครื่องหมาย live-appName.properties ไปที่ appName.properties อาจมีไฟล์นามสกุลอื่น ๆ เช่นเดียวกับ live-appName.xml

ด้านล่างเป็นรหัสที่ใช้งานได้สำหรับสิ่งนี้ซึ่งค้นหาไฟล์ในไดเรกทอรีที่กำหนด (ระดับซ้อน) แล้วเปลี่ยนชื่อ (ย้าย) เป็นชื่อไฟล์ที่ต้องการ

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)

ฟังก์ชั่นนี้เรียกว่าจากสคริปต์หลัก

flipProperties(searchDir)

หวังว่านี่จะช่วยให้บางคนประสบปัญหาคล้ายกัน


1

รุ่นของคำตอบของโยฮันดาห์ลินที่เรียบง่ายโดยไม่ต้อง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']

1

นี่คือวิธีการแก้ปัญหาของฉันโดยใช้รายการความเข้าใจเพื่อค้นหาหลายนามสกุลไฟล์ซ้ำในไดเรกทอรีและไดเรกทอรีย่อยทั้งหมด:

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

ฉันแก้ไขคำตอบแรกสุดในการโพสต์นี้และเพิ่งสร้างสคริปต์นี้ซึ่งจะวนซ้ำไฟล์ทั้งหมดในไดเรกทอรีที่กำหนด (searchdir) และไดเรกทอรีย่อยที่อยู่ใต้มัน ... และพิมพ์ชื่อไฟล์ rootdir วันที่แก้ไข / สร้างและ ขนาด.

หวังว่านี้จะช่วยให้ใครบางคน ... และพวกเขาสามารถเดินไดเรกทอรีและรับไฟล์info

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

นี่คือโซลูชันที่จะจับคู่รูปแบบกับพา ธ เต็มไม่ใช่เฉพาะชื่อไฟล์ฐาน

มันใช้ fnmatch.translateในการแปลงรูปแบบแบบกลมเป็นนิพจน์ปกติซึ่งจะถูกจับคู่กับพา ธ เต็มของแต่ละไฟล์ที่พบในขณะที่เดินไปที่สารบบ

re.IGNORECASEเป็นทางเลือก แต่เป็นที่ต้องการบน Windows เนื่องจากระบบไฟล์นั้นไม่คำนึงถึงขนาดตัวพิมพ์ (ฉันไม่ได้กังวลกับการรวบรวม regex เพราะ docs ระบุว่ามันควรจะเก็บไว้ภายใน)

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

ฉันต้องการวิธีแก้ปัญหาสำหรับpython 2.xที่ทำงานได้รวดเร็วในไดเรกทอรีขนาดใหญ่
ฉันลงเอยด้วยสิ่งนี้:

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

โปรดทราบว่าคุณอาจต้องการการจัดการข้อยกเว้นในกรณีที่lsไม่พบไฟล์ที่ตรงกัน


ฉันเพิ่งรู้ว่าls src/**/*.cใช้งานได้เฉพาะเมื่อเปิดใช้งานตัวเลือก globstar ( shopt -s globstar) - ดูคำตอบนี้สำหรับรายละเอียด
โรมัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.