Pencarian sub folder rekursif dan mengembalikan file dalam daftar python

118

Saya sedang mengerjakan skrip untuk secara rekursif menelusuri subfolder di folder utama dan membuat daftar dari jenis file tertentu. Saya mengalami masalah dengan skrip. Saat ini ditetapkan sebagai berikut

for root, subFolder, files in os.walk(PATH):
    for item in files:
        if item.endswith(".txt") :
            fileNamePath = str(os.path.join(root,subFolder,item))

Masalahnya adalah bahwa variabel subFolder menarik daftar subfolder daripada folder tempat file ITEM berada. Saya berpikir untuk menjalankan perulangan for untuk subfolder sebelumnya dan bergabung dengan bagian pertama dari jalur tetapi saya pikir saya akan memeriksa ulang untuk melihat apakah ada yang punya saran sebelum itu. Terima kasih atas bantuan Anda!

pengguna2709514
sumber

Jawaban:

156

Anda harus menggunakan dirpathyang Anda panggil root. Ini dirnamesdisediakan sehingga Anda dapat memangkasnya jika ada folder yang tidak ingin Anda os.walkkembalikan.

import os
result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(PATH) for f in filenames if os.path.splitext(f)[1] == '.txt']

Edit:

Setelah downvote terbaru, menurut saya itu globadalah alat yang lebih baik untuk memilih berdasarkan ekstensi.

import os
from glob import glob
result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]

Juga versi generator

from itertools import chain
result = (chain.from_iterable(glob(os.path.join(x[0], '*.txt')) for x in os.walk('.')))

Edit2 untuk Python 3.4+

from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))
John La Rooy
sumber
1
Pola glob '*. [Tt] [Xx] [Tt]' akan membuat pencarian case-insensitive.
SergiyKolesnikov
@SergiyKolesnikov, Terima kasih, saya telah menggunakannya di edit di bagian bawah. Perhatikan bahwa rglobini tidak sensitif pada platform Windows - tetapi itu tidak sensitif secara portabel.
John La Rooy
1
@JohnLaRooy It works with globtoo (Python 3.6 here):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
SergiyKolesnikov
@Sergiy: Anda iglobtidak bekerja untuk file di sub-sub folder atau di bawahnya. Anda perlu menambahkan recursive=True.
pengguna136036
1
@ user136036, "lebih baik" tidak selalu berarti tercepat. Terkadang keterbacaan dan pemeliharaan juga penting.
John La Rooy
111

Diubah dengan Python 3.5 : Dukungan untuk glob rekursif menggunakan "**".

glob.glob()mendapat parameter rekursif baru .

Jika Anda ingin mendapatkan setiap .txtfile di bawah my_path(secara rekursif termasuk subdirektori):

import glob

files = glob.glob(my_path + '/**/*.txt', recursive=True)

# my_path/     the dir
# **/       every file and dir under my_path
# *.txt     every file that ends with '.txt'

Jika Anda membutuhkan iterator, Anda dapat menggunakan iglob sebagai alternatif:

for file in glob.iglob(my_path, recursive=False):
    # ...
Rotareti
sumber
1
TypeError: glob () mendapat argumen kata kunci yang tidak terduga 'rekursif'
CyberJacob
1
Ini harus bekerja. Pastikan Anda menggunakan versi> = 3.5. Saya menambahkan tautan ke dokumentasi dalam jawaban saya untuk detail lebih lanjut.
Rotareti
Itulah sebabnya, saya berada di 2.7
CyberJacob
1
Mengapa pemahaman daftar dan bukan hanya files = glob.glob(PATH + '/*/**/*.txt', recursive=True)?
tobltobs
Ups! :) Ini benar-benar berlebihan. Tidak tahu apa yang membuat saya menulis seperti itu. Terima kasih telah menyebutkannya! Saya akan memperbaikinya.
Rotareti
20

Saya akan menerjemahkan pemahaman daftar John La Rooy menjadi bersarang, kalau-kalau ada orang lain yang kesulitan memahaminya.

result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]

Harus setara dengan:

import glob

result = []

for x in os.walk(PATH):
    for y in glob.glob(os.path.join(x[0], '*.txt')):
        result.append(y)

Berikut dokumentasi untuk daftar pemahaman dan fungsi os.walk dan glob.glob .

Jefferson Lima
sumber
1
Jawaban ini berhasil untuk saya dengan Python 3.7.3. glob.glob(..., recursive=True)dan list(Path(dir).glob(...'))tidak.
miguelmorin
11

Ini tampaknya menjadi solusi tercepat yang dapat saya temukan, dan lebih cepat daripada os.walkdan jauh lebih cepat daripada globsolusi apa pun .

  • Ini juga akan memberi Anda daftar semua subfolder bersarang pada dasarnya tanpa biaya.
  • Anda dapat mencari beberapa ekstensi berbeda.
  • Anda juga dapat memilih untuk mengembalikan jalur lengkap atau hanya nama file dengan mengubahnya f.pathke f.name(jangan ubah untuk subfolder!).

Args: dir: str, ext: list.
Fungsi mengembalikan dua daftar:subfolders, files .

Lihat di bawah untuk analisis kecepatan rinci.

def run_fast_scandir(dir, ext):    # dir: str, ext: list
    subfolders, files = [], []

    for f in os.scandir(dir):
        if f.is_dir():
            subfolders.append(f.path)
        if f.is_file():
            if os.path.splitext(f.name)[1].lower() in ext:
                files.append(f.path)


    for dir in list(subfolders):
        sf, f = run_fast_scandir(dir, ext)
        subfolders.extend(sf)
        files.extend(f)
    return subfolders, files


subfolders, files = run_fast_scandir(folder, [".jpg"])


Analisis kecepatan

untuk berbagai metode untuk mendapatkan semua file dengan ekstensi file tertentu di dalam semua subfolder dan folder utama.

tl; dr:
- fast_scandirjelas menang dan dua kali lebih cepat dari semua solusi lainnya, kecuali os.walk.
- os.walkPosisi kedua sedikit lebih lambat.
- menggunakan globakan sangat memperlambat proses.
- Tidak ada hasil yang menggunakan pemilahan alami . Artinya, hasil akan diurutkan seperti ini: 1, 10, 2. Untuk mendapatkan penyortiran alami (1, 2, 10), lihat https://stackoverflow.com/a/48030307/2441026


Hasil:

fast_scandir    took  499 ms. Found files: 16596. Found subfolders: 439
os.walk         took  589 ms. Found files: 16596
find_files      took  919 ms. Found files: 16596
glob.iglob      took  998 ms. Found files: 16596
glob.glob       took 1002 ms. Found files: 16596
pathlib.rglob   took 1041 ms. Found files: 16596
os.walk-glob    took 1043 ms. Found files: 16596

Pengujian dilakukan dengan menjalankan W7x64, Python 3.8.1, 20. 16596 berkas dalam 439 subfolder (sebagian bersarang).
find_filesberasal dari https://stackoverflow.com/a/45646357/2441026 dan memungkinkan Anda mencari beberapa ekstensi.
fast_scandirditulis sendiri dan juga akan mengembalikan daftar subfolder. Anda dapat memberikannya daftar ekstensi untuk dicari (saya menguji daftar dengan satu entri ke yang sederhana if ... == ".jpg"dan tidak ada perbedaan yang signifikan).


# -*- coding: utf-8 -*-
# Python 3


import time
import os
from glob import glob, iglob
from pathlib import Path


directory = r"<folder>"
RUNS = 20


def run_os_walk():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [os.path.join(dp, f) for dp, dn, filenames in os.walk(directory) for f in filenames if
                  os.path.splitext(f)[1].lower() == '.jpg']
    print(f"os.walk\t\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_os_walk_glob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [y for x in os.walk(directory) for y in glob(os.path.join(x[0], '*.jpg'))]
    print(f"os.walk-glob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_glob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = glob(os.path.join(directory, '**', '*.jpg'), recursive=True)
    print(f"glob.glob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_iglob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = list(iglob(os.path.join(directory, '**', '*.jpg'), recursive=True))
    print(f"glob.iglob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_pathlib_rglob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = list(Path(directory).rglob("*.jpg"))
    print(f"pathlib.rglob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def find_files(files, dirs=[], extensions=[]):
    # https://stackoverflow.com/a/45646357/2441026

    new_dirs = []
    for d in dirs:
        try:
            new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
        except OSError:
            if os.path.splitext(d)[1].lower() in extensions:
                files.append(d)

    if new_dirs:
        find_files(files, new_dirs, extensions )
    else:
        return


def run_fast_scandir(dir, ext):    # dir: str, ext: list
    # https://stackoverflow.com/a/59803793/2441026

    subfolders, files = [], []

    for f in os.scandir(dir):
        if f.is_dir():
            subfolders.append(f.path)
        if f.is_file():
            if os.path.splitext(f.name)[1].lower() in ext:
                files.append(f.path)


    for dir in list(subfolders):
        sf, f = run_fast_scandir(dir, ext)
        subfolders.extend(sf)
        files.extend(f)
    return subfolders, files



if __name__ == '__main__':
    run_os_walk()
    run_os_walk_glob()
    run_glob()
    run_iglob()
    run_pathlib_rglob()


    a = time.time_ns()
    for i in range(RUNS):
        files = []
        find_files(files, dirs=[directory], extensions=[".jpg"])
    print(f"find_files\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}")


    a = time.time_ns()
    for i in range(RUNS):
        subf, files = run_fast_scandir(directory, [".jpg"])
    print(f"fast_scandir\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}. Found subfolders: {len(subf)}")
pengguna136036
sumber
10

pathlibPerpustakaan baru menyederhanakan ini menjadi satu baris:

from pathlib import Path
result = list(Path(PATH).glob('**/*.txt'))

Anda juga dapat menggunakan versi generator:

from pathlib import Path
for file in Path(PATH).glob('**/*.txt'):
    pass

Ini mengembalikan Pathobjek, yang dapat Anda gunakan untuk hampir semua hal, atau mendapatkan nama file sebagai string file.name.

Emre
sumber
6

Ini bukan jawaban yang paling tajam, tapi saya akan menaruhnya di sini untuk bersenang-senang karena ini adalah pelajaran rekursi yang rapi

def find_files( files, dirs=[], extensions=[]):
    new_dirs = []
    for d in dirs:
        try:
            new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
        except OSError:
            if os.path.splitext(d)[1] in extensions:
                files.append(d)

    if new_dirs:
        find_files(files, new_dirs, extensions )
    else:
        return

Di komputer saya, saya memiliki dua folder, rootdanroot2

mender@multivax ]ls -R root root2
root:
temp1 temp2

root/temp1:
temp1.1 temp1.2

root/temp1/temp1.1:
f1.mid

root/temp1/temp1.2:
f.mi  f.mid

root/temp2:
tmp.mid

root2:
dummie.txt temp3

root2/temp3:
song.mid

Katakanlah saya ingin mencari semua .txtdan semua .midfile di salah satu direktori ini, maka saya bisa melakukannya

files = []
find_files( files, dirs=['root','root2'], extensions=['.mid','.txt'] )
print(files)

#['root2/dummie.txt',
# 'root/temp2/tmp.mid',
# 'root2/temp3/song.mid',
# 'root/temp1/temp1.1/f1.mid',
# 'root/temp1/temp1.2/f.mid']
kulit
sumber
4

Rekursif baru di Python 3.5, jadi tidak akan berfungsi di Python 2.7. Berikut adalah contoh yang menggunakan rstring sehingga Anda hanya perlu memberikan jalur seperti pada Win, Lin, ...

import glob

mypath=r"C:\Users\dj\Desktop\nba"

files = glob.glob(mypath + r'\**\*.py', recursive=True)
# print(files) # as list
for f in files:
    print(f) # nice looking single line per file

Catatan: Ini akan mencantumkan semua file, tidak peduli seberapa dalam itu harus disimpan.

prosti
sumber
3

Anda dapat melakukannya dengan cara ini untuk mengembalikan Anda daftar file jalur absolut.

def list_files_recursive(path):
    """
    Function that receives as a parameter a directory path
    :return list_: File List and Its Absolute Paths
    """

    import os

    files = []

    # r = root, d = directories, f = files
    for r, d, f in os.walk(path):
        for file in f:
            files.append(os.path.join(r, file))

    lst = [file for file in files]
    return lst


if __name__ == '__main__':

    result = list_files_recursive('/tmp')
    print(result)
WilliamCanin
sumber
3

Jika Anda tidak keberatan memasang perpustakaan cahaya tambahan, Anda dapat melakukan ini:

pip install plazy

Pemakaian:

import plazy

txt_filter = lambda x : True if x.endswith('.txt') else False
files = plazy.list_files(root='data', filter_func=txt_filter, is_include_root=True)

Hasilnya akan terlihat seperti ini:

['data/a.txt', 'data/b.txt', 'data/sub_dir/c.txt']

Ia bekerja pada Python 2.7 dan Python 3.

Github: https://github.com/kyzas/plazy#list-files

Penafian: Saya seorang penulis dari plazy.

Minh Nguyen
sumber
1

Fungsi ini hanya akan memasukkan file ke dalam daftar secara rekursif. Semoga ini bisa.

import os


def ls_files(dir):
    files = list()
    for item in os.listdir(dir):
        abspath = os.path.join(dir, item)
        try:
            if os.path.isdir(abspath):
                files = files + ls_files(abspath)
            else:
                files.append(abspath)
        except FileNotFoundError as err:
            print('invalid directory\n', 'Error: ', err)
    return files
Yossarian42
sumber
0

Solusi asli Anda hampir benar, tetapi variabel "root" diperbarui secara dinamis saat berada di sekitar jalur rekursif. os.walk () adalah generator rekursif. Setiap set tuple (root, subFolder, files) ditujukan untuk root tertentu seperti cara Anda mengaturnya.

yaitu

root = 'C:\\'
subFolder = ['Users', 'ProgramFiles', 'ProgramFiles (x86)', 'Windows', ...]
files = ['foo1.txt', 'foo2.txt', 'foo3.txt', ...]

root = 'C:\\Users\\'
subFolder = ['UserAccount1', 'UserAccount2', ...]
files = ['bar1.txt', 'bar2.txt', 'bar3.txt', ...]

...

Saya membuat sedikit perubahan pada kode Anda untuk mencetak daftar lengkap.

import os
for root, subFolder, files in os.walk(PATH):
    for item in files:
        if item.endswith(".txt") :
            fileNamePath = str(os.path.join(root,item))
            print(fileNamePath)

Semoga ini membantu!

LastTigerEyes
sumber