Ricerca ricorsiva di sottocartelle e restituzione di file in un elenco python


118

Sto lavorando a uno script per scorrere ricorsivamente le sottocartelle in una cartella principale e creare un elenco di un determinato tipo di file. Ho un problema con la sceneggiatura. Attualmente è impostato come segue

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

il problema è che la variabile sottocartella sta estraendo un elenco di sottocartelle anziché la cartella in cui si trova il file ITEM. Stavo pensando di eseguire un ciclo for per la sottocartella prima e di unirmi alla prima parte del percorso, ma ho pensato di ricontrollare Id per vedere se qualcuno ha qualche suggerimento prima. Grazie per l'aiuto!

Risposte:


156

Dovresti usare il dirpathche chiami root. Il dirnamesvengono forniti in modo da poter potare se ci sono le cartelle che non si desidera os.walkla ricorsione in.

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

Modificare:

Dopo l'ultimo downvote, mi è venuto in mente che globè uno strumento migliore per la selezione per estensione.

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

Anche una versione generatore

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

Edit2 per Python 3.4+

from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))

1
'*. [Tt] [Xx] [Tt]' glob pattern renderà la ricerca senza distinzione tra maiuscole e minuscole.
SergiyKolesnikov

@SergiyKolesnikov, grazie, l'ho usato nella modifica in basso. Nota che rglobè insensibile su piattaforme Windows, ma non è portabile insensibile.
John La Rooy,

1
@JohnLaRooy Funziona globanche con (Python 3.6 qui):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
SergiyKolesnikov

@Sergiy: il tuo iglobnon funziona per i file nelle sotto-sottocartelle o sotto. Devi aggiungere recursive=True.
user136036

1
@ user136036, "meglio" non sempre significa più veloce. A volte anche la leggibilità e la manutenibilità sono importanti.
John La Rooy,

111

Modificato in Python 3.5 : supporto per glob ricorsivi utilizzando "**".

glob.glob()ha ottenuto un nuovo parametro ricorsivo .

Se vuoi ottenere tutti i .txtfile sotto my_path(includendo ricorsivamente le sottodirectory):

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'

Se hai bisogno di un iteratore puoi usare iglob come alternativa:

for file in glob.iglob(my_path, recursive=False):
    # ...

1
TypeError: glob () ha ricevuto un argomento parola chiave imprevisto "ricorsivo"
CyberJacob

1
Dovrebbe funzionare. Assicurati di utilizzare una versione> = 3.5. Ho aggiunto un collegamento alla documentazione nella mia risposta per maggiori dettagli.
Rotareti

Ecco perché, sono su 2.7
CyberJacob

1
Perché la lista di comprensione e non solo files = glob.glob(PATH + '/*/**/*.txt', recursive=True)?
tobltobs

Ops! :) È totalmente ridondante. Non ho idea di cosa mi abbia fatto scrivere così. Grazie per averlo menzionato! Lo aggiusterò.
Rotareti

20

Tradurrò di lista di John La Rooy alla nidificato per di, nel caso in cui nessun altro ha difficoltà a capire che.

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

Dovrebbe essere equivalente a:

import glob

result = []

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

Ecco la documentazione per la comprensione delle liste e le funzioni os.walk e glob.glob .


1
Questa risposta ha funzionato per me in Python 3.7.3. glob.glob(..., recursive=True)e list(Path(dir).glob(...'))non lo fece.
miguelmorin

11

Questa sembra essere la soluzione più veloce che potrei trovare, ed è più veloce os.walke molto più veloce di qualsiasi globsoluzione .

  • Ti fornirà anche un elenco di tutte le sottocartelle nidificate praticamente senza alcun costo.
  • Puoi cercare diverse estensioni differenti.
  • Puoi anche scegliere di restituire i percorsi completi o solo i nomi dei file cambiando f.pathin f.name(non cambiarlo per le sottocartelle!).

Args: dir: str, ext: list.
La funzione restituisce due elenchi:subfolders, files .

Vedi sotto per un'analisi dettagliata della velocità.

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"])


Analisi della velocità

per vari metodi per ottenere tutti i file con un'estensione di file specifica all'interno di tutte le sottocartelle e nella cartella principale.

tl; dr:
- fast_scandirvince chiaramente ed è due volte più veloce di tutte le altre soluzioni, tranne os.walk.
- os.walkè il secondo posto leggermente più lento.
- l'utilizzo globrallenterà notevolmente il processo.
- Nessuno dei risultati utilizza l'ordinamento naturale . Ciò significa che i risultati verranno ordinati in questo modo: 1, 10, 2. Per ottenere un ordinamento naturale (1, 2, 10), dai un'occhiata a https://stackoverflow.com/a/48030307/2441026


risultati:

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

I test sono stati eseguiti con W7x64, Python 3.8.1, 20 esecuzioni. 16596 file in 439 sottocartelle (parzialmente nidificate).
find_filesproviene da https://stackoverflow.com/a/45646357/2441026 e ti consente di cercare diverse estensioni.
fast_scandirè stato scritto da me stesso e restituirà anche un elenco di sottocartelle. Puoi dargli un elenco di estensioni da cercare (ho testato un elenco con una voce per un semplice if ... == ".jpg"e non c'era alcuna differenza significativa).


# -*- 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)}")

10

La nuova pathliblibreria lo semplifica in una riga:

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

Puoi anche usare la versione del generatore:

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

Questo restituisce Pathoggetti, che puoi usare praticamente per qualsiasi cosa, o ottieni il nome del file come una stringa da file.name.


6

Non è la risposta più pitonica, ma la metterò qui per divertimento perché è una bella lezione di ricorsione

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

Sulla mia macchina ho due cartelle rooteroot2

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

Diciamo che voglio trovare tutti .txti .midfile in una di queste directory, quindi posso semplicemente farlo

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

4

Ricorsivo è una novità in Python 3.5, quindi non funzionerà su Python 2.7. Ecco l'esempio che utilizza le rstringhe, quindi devi solo fornire il percorso così com'è su 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

Nota: elencherà tutti i file, non importa quanto in profondità dovrebbe andare.


3

Puoi farlo in questo modo per restituirti un elenco di file di percorsi assoluti.

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)

3

Se non ti dispiace installare una libreria di luci aggiuntiva, puoi farlo:

pip install plazy

Uso:

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)

Il risultato dovrebbe essere simile a questo:

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

Funziona sia su Python 2.7 che su Python 3.

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

Disclaimer: sono un autore di plazy.


1

Questa funzione inserirà ricorsivamente solo i file in un elenco. Spero che tu lo faccia.

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

0

La tua soluzione originale era quasi corretta, ma la variabile "root" viene aggiornata dinamicamente mentre si muove in modo ricorsivo. os.walk () è un generatore ricorsivo. Ogni set di tupla di (root, sottocartella, file) è per una radice specifica nel modo in cui è stata configurata.

vale a dire

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', ...]

...

Ho apportato una leggera modifica al tuo codice per stampare un elenco completo.

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)

Spero che questo ti aiuti!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.