Rekursive Suche nach Unterordnern und Rückgabe von Dateien in einer Listenpython

117

Ich arbeite an einem Skript, um rekursiv Unterordner in einem Hauptordner zu durchsuchen und eine Liste aus einem bestimmten Dateityp zu erstellen. Ich habe ein Problem mit dem Skript. Es ist derzeit wie folgt eingestellt

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

Das Problem besteht darin, dass die Variable subFolder eine Liste von Unterordnern anstelle des Ordners abruft, in dem sich die ITEM-Datei befindet. Ich dachte daran, vorher eine for-Schleife für den Unterordner auszuführen und den ersten Teil des Pfads zu verbinden, aber ich dachte mir, ich würde noch einmal prüfen, ob jemand vorher irgendwelche Vorschläge hat. Danke für Ihre Hilfe!

user2709514
quelle

Antworten:

154

Sie sollten das verwenden, dirpathwas Sie anrufen root. Sie dirnameswerden mitgeliefert, damit Sie sie beschneiden können, wenn es Ordner gibt, in die Sie nicht zurückkehren möchten os.walk.

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

Bearbeiten:

Nach der letzten Abwertung fiel mir ein, dass dies globein besseres Werkzeug für die Auswahl nach Erweiterung ist.

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

Auch eine Generatorversion

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

Edit2 für Python 3.4+

from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))
John La Rooy
quelle
1
Das Glob-Muster '*. [Tt] [Xx] [Tt]' macht die Suche unabhängig von Groß- und Kleinschreibung.
SergiyKolesnikov
@SergiyKolesnikov, Danke, das habe ich in der Bearbeitung unten verwendet. Beachten Sie, dass das rglobauf Windows-Plattformen unempfindlich ist - aber nicht unempfindlich.
John La Rooy
1
@ JohnLaRooy Es funktioniert auch mit glob(Python 3.6 hier):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
SergiyKolesnikov
@Sergiy: Ihr iglobfunktioniert nicht für Dateien in Unterordnern oder darunter. Sie müssen hinzufügen recursive=True.
user136036
1
@ user136036, "besser" bedeutet nicht immer am schnellsten. Manchmal sind auch Lesbarkeit und Wartbarkeit wichtig.
John La Rooy
109

In Python 3.5 geändert : Unterstützung für rekursive Globs mit "**".

glob.glob()habe einen neuen rekursiven Parameter .

Wenn Sie jede .txtDatei unter my_path(rekursiv einschließlich Unterverzeichnisse) erhalten möchten:

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'

Wenn Sie einen Iterator benötigen, können Sie alternativ iglob verwenden :

for file in glob.iglob(my_path, recursive=False):
    # ...
Rotareti
quelle
1
TypeError: glob () hat ein unerwartetes Schlüsselwortargument 'rekursiv' erhalten
CyberJacob
1
Es sollte funktionieren. Stellen Sie sicher, dass Sie eine Version> = 3.5 verwenden. Ich habe in meiner Antwort einen Link zur Dokumentation hinzugefügt, um weitere Einzelheiten zu erfahren.
Rotareti
Deshalb bin ich am 2.7
CyberJacob
1
Warum das Listenverständnis und nicht nur files = glob.glob(PATH + '/*/**/*.txt', recursive=True)?
tobltobs
Hoppla! :) Es ist völlig überflüssig. Keine Ahnung, warum ich es so geschrieben habe. Danke, dass du es erwähnt hast! Ich werde es reparieren.
Rotareti
19

Ich werde das Listenverständnis von John La Rooy in verschachtelte für übersetzen, nur für den Fall, dass jemand anderes Probleme hat, es zu verstehen.

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

Sollte gleichbedeutend sein mit:

import glob

result = []

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

Hier ist die Dokumentation zum Listenverständnis und die Funktionen os.walk und glob.glob .

Jefferson Lima
quelle
1
Diese Antwort hat bei mir in Python 3.7.3 funktioniert. glob.glob(..., recursive=True)und list(Path(dir).glob(...'))nicht.
Miguelmorin
11

Dies scheint die schnellste Lösung zu sein, die ich finden konnte, und sie ist schneller als os.walkund viel schneller als jede globandere Lösung .

  • Außerdem erhalten Sie kostenlos eine Liste aller verschachtelten Unterordner.
  • Sie können nach verschiedenen Erweiterungen suchen.
  • Sie können auch durch Änderung entweder vollständige Pfade oder einfach nur die Namen für die Dateien zurückzukehren wählen f.pathzu f.name(nicht ändern , es für Unterordner!).

Argumente : dir: str, ext: list.
Die Funktion gibt zwei Listen zurück : subfolders, files.

Unten finden Sie eine detaillierte Geschwindigkeitsanalyse.

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


Geschwindigkeitsanalyse

für verschiedene Methoden, um alle Dateien mit einer bestimmten Dateierweiterung in allen Unterordnern und im Hauptordner abzurufen.

tl; dr:
- fast_scandirgewinnt eindeutig und ist doppelt so schnell wie alle anderen Lösungen außer os.walk.
- os.walkist der zweite Platz etwas langsamer.
- Die Verwendung globverlangsamt den Prozess erheblich.
- Keines der Ergebnisse verwendet eine natürliche Sortierung . Dies bedeutet, dass die Ergebnisse wie folgt sortiert werden: 1, 10, 2. Um eine natürliche Sortierung (1, 2, 10) zu erhalten, besuchen Sie bitte https://stackoverflow.com/a/48030307/2441026


Ergebnisse:

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

Die Tests wurden mit W7x64, Python 3.8.1, 20 Läufen durchgeführt. 16596 Dateien in 439 (teilweise verschachtelten) Unterordnern.
find_filesstammt von https://stackoverflow.com/a/45646357/2441026 und ermöglicht die Suche nach mehreren Erweiterungen.
fast_scandirwurde von mir geschrieben und wird auch eine Liste von Unterordnern zurückgeben. Sie können ihm eine Liste mit Erweiterungen geben, nach denen gesucht werden soll (ich habe eine Liste mit einem Eintrag zu einem einfachen getestet if ... == ".jpg"und es gab keinen signifikanten Unterschied).


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

Die neue pathlibBibliothek vereinfacht dies auf eine Zeile:

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

Sie können auch die Generatorversion verwenden:

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

Dies gibt PathObjekte zurück, die Sie für so ziemlich alles verwenden können, oder Sie erhalten den Dateinamen als Zeichenfolge von file.name.

Emre
quelle
6

Es ist nicht die pythonischste Antwort, aber ich werde es hier zum Spaß einfügen, weil es eine nette Lektion in Rekursion ist

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

Auf meinem Computer habe ich zwei Ordner rootundroot2

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

Nehmen wir an, ich möchte .txtalle .midDateien in einem dieser Verzeichnisse finden, dann kann ich es einfach tun

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']
dermen
quelle
4

Rekursiv ist neu in Python 3.5, daher funktioniert es unter Python 2.7 nicht. Hier ist das Beispiel, in dem rZeichenfolgen verwendet werden , sodass Sie nur den Pfad angeben müssen, der entweder für Win, Lin, ... gilt.

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

Hinweis: Es werden alle Dateien aufgelistet, egal wie tief sie gehen sollen.

Prosti
quelle
3

Auf diese Weise können Sie eine Liste der absoluten Pfaddateien zurückgeben.

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

Wenn es Ihnen nichts ausmacht, eine zusätzliche Lichtbibliothek zu installieren, können Sie dies tun:

pip install plazy

Verwendung:

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)

Das Ergebnis sollte ungefähr so ​​aussehen:

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

Es funktioniert sowohl mit Python 2.7 als auch mit Python 3.

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

Haftungsausschluss: Ich bin Autor von plazy.

Minh Nguyen
quelle
1

Diese Funktion fügt rekursiv nur Dateien in eine Liste ein. Hoffe das wirst du.

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

Ihre ursprüngliche Lösung war nahezu korrekt, aber die Variable "root" wird dynamisch aktualisiert, wenn sie rekursiv umhergeht. os.walk () ist ein rekursiver Generator. Jeder Tupel-Satz (root, subFolder, files) ist für ein bestimmtes root so, wie Sie es eingerichtet haben.

dh

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

...

Ich habe Ihren Code leicht angepasst, um eine vollständige Liste zu drucken.

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)

Hoffe das hilft!

LastTigerEyes
quelle