Python rekursiver Ordner gelesen

225

Ich habe einen C ++ / Obj-C-Hintergrund und entdecke gerade Python (schreibe es seit ungefähr einer Stunde). Ich schreibe ein Skript, um den Inhalt von Textdateien in einer Ordnerstruktur rekursiv zu lesen.

Das Problem, das ich habe, ist, dass der Code, den ich geschrieben habe, nur für einen Ordner tief funktioniert. Ich kann sehen, warum ich im Code (siehe #hardcoded path) nicht weiß, wie ich mit Python vorankommen kann, da meine Erfahrung damit nur brandneu ist.

Python-Code:

import os
import sys

rootdir = sys.argv[1]

for root, subFolders, files in os.walk(rootdir):

    for folder in subFolders:
        outfileName = rootdir + "/" + folder + "/py-outfile.txt" # hardcoded path
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName

        for file in files:
            filePath = rootdir + '/' + file
            f = open( filePath, 'r' )
            toWrite = f.read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
            f.close()

        folderOut.close()
Brock Woolf
quelle

Antworten:

347

Stellen Sie sicher, dass Sie die drei Rückgabewerte von os.walk:

for root, subdirs, files in os.walk(rootdir):

hat folgende Bedeutung:

  • root: Aktueller Pfad, der "durchlaufen" wird
  • subdirs: Dateien im rootTypverzeichnis
  • files: Dateien in root(nicht in subdirs) einem anderen Typ als dem Verzeichnis

Und bitte verwenden, os.path.joinanstatt mit einem Schrägstrich zu verketten! Ihr Problem ist filePath = rootdir + '/' + file- Sie müssen den aktuell "gehenden" Ordner anstelle des obersten Ordners verketten. Das muss also sein filePath = os.path.join(root, file). Übrigens ist "Datei" eine integrierte Datei, daher verwenden Sie sie normalerweise nicht als Variablennamen.

Ein weiteres Problem sind Ihre Schleifen, die zum Beispiel so aussehen sollten:

import os
import sys

walk_dir = sys.argv[1]

print('walk_dir = ' + walk_dir)

# If your current working directory may change during script execution, it's recommended to
# immediately convert program arguments to an absolute path. Then the variable root below will
# be an absolute path as well. Example:
# walk_dir = os.path.abspath(walk_dir)
print('walk_dir (absolute) = ' + os.path.abspath(walk_dir))

for root, subdirs, files in os.walk(walk_dir):
    print('--\nroot = ' + root)
    list_file_path = os.path.join(root, 'my-directory-list.txt')
    print('list_file_path = ' + list_file_path)

    with open(list_file_path, 'wb') as list_file:
        for subdir in subdirs:
            print('\t- subdirectory ' + subdir)

        for filename in files:
            file_path = os.path.join(root, filename)

            print('\t- file %s (full path: %s)' % (filename, file_path))

            with open(file_path, 'rb') as f:
                f_content = f.read()
                list_file.write(('The file %s contains:\n' % filename).encode('utf-8'))
                list_file.write(f_content)
                list_file.write(b'\n')

Wenn Sie es nicht wussten, ist die withAnweisung für Dateien eine Abkürzung:

with open('filename', 'rb') as f:
    dosomething()

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()
AndiDog
quelle
4
Hervorragend, viele Drucke, um zu verstehen, was los ist, und es funktioniert perfekt. Vielen Dank! +1
Brock Woolf
16
Kopf an alle, die so dumm / ahnungslos sind wie ich ... Dieses Codebeispiel schreibt eine txt-Datei in jedes Verzeichnis. Ich bin froh, dass ich es in einem
versionierten
Das zweite (längste) Code-Snippet hat sehr gut funktioniert und mir viel langweilige Arbeit
Amphibient
1
Da Geschwindigkeit, wenn auch offensichtlich der wichtigste Aspekt, os.walknicht schlecht ist, obwohl ich mir einen noch schnelleren Weg über ausgedacht habe os.scandir. Alle globLösungen sind viel langsamer als walk& scandir. Meine Funktion sowie eine vollständige Geschwindigkeitsanalyse finden Sie hier: stackoverflow.com/a/59803793/2441026
user136036
112

Wenn Sie Python 3.5 oder höher verwenden, können Sie dies in einer Zeile erledigen.

import glob

for filename in glob.iglob(root_dir + '**/*.txt', recursive=True):
     print(filename)

Wie in der Dokumentation erwähnt

Wenn rekursiv wahr ist, stimmt das Muster '**' mit allen Dateien und null oder mehr Verzeichnissen und Unterverzeichnissen überein.

Wenn Sie jede Datei möchten, können Sie verwenden

import glob

for filename in glob.iglob(root_dir + '**/*', recursive=True):
     print(filename)
ChillarAnand
quelle
TypeError: iglob () hat ein unerwartetes Schlüsselwortargument 'rekursiv' erhalten
Jewenile
1
Wie eingangs erwähnt, ist es nur für Python 3.5+
ChillarAnand
9
root_dir muss einen abschließenden Schrägstrich haben (andernfalls erhalten Sie als erstes Argument "Ordner ** / *" anstelle von "Ordner / ** / *"). Sie können os.path.join (root_dir, ' * / ') verwenden, aber ich weiß nicht, ob es akzeptabel ist, os.path.join mit Platzhalterpfaden zu verwenden (dies funktioniert jedoch für meine Anwendung).
Drojf
@ChillarAnand Können Sie dem Code in dieser Antwort einen Kommentar hinzufügen, der einen abschließenden root_dirSchrägstrich benötigt? Dies spart den Leuten Zeit (oder zumindest hätte es mir Zeit gespart). Vielen Dank.
Dan Nissenbaum
1
Wenn ich dies wie in der Antwort ausgeführt habe, hat es nicht rekursiv funktioniert. Um diese Arbeit rekursiv zu machen, musste ich sie ändern in : glob.iglob(root_dir + '**/**', recursive=True). Ich arbeite in Python 3.8.2
Mikey
38

Wenn Sie mit Dave Webb einverstanden sind, os.walkerhalten Sie für jedes Verzeichnis im Baum ein Element. Tatsache ist, dass Sie sich einfach nicht darum kümmern müssen subFolders.

Code wie dieser sollte funktionieren:

import os
import sys

rootdir = sys.argv[1]

for folder, subs, files in os.walk(rootdir):
    with open(os.path.join(folder, 'python-outfile.txt'), 'w') as dest:
        for filename in files:
            with open(os.path.join(folder, filename), 'r') as src:
                dest.write(src.read())
Clément
quelle
3
Schön. Das funktioniert auch. Ich bevorzuge jedoch die Version von AndiDog, obwohl sie länger ist, weil es klarer ist, Python als Anfänger zu verstehen. +1
Brock Woolf
20

TL; DR: Dies entspricht dem find -type fDurchsuchen aller Dateien in allen Ordnern unten und einschließlich der aktuellen:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Wie bereits in anderen Antworten erwähnt, os.walk()ist die Antwort, aber es könnte besser erklärt werden. Es ist ganz einfach! Gehen wir durch diesen Baum:

docs/
└── doc1.odt
pics/
todo.txt

Mit diesem Code:

for currentpath, folders, files in os.walk('.'):
    print(currentpath)

Dies currentpathist der aktuelle Ordner, den es sich ansieht. Dies wird Folgendes ausgeben:

.
./docs
./pics

Es wird also dreimal wiederholt, da drei Ordner vorhanden sind: der aktuelle ,, docsund pics. In jeder Schleife werden die Variablen foldersund filesalle Ordner und Dateien gefüllt. Zeigen wir ihnen:

for currentpath, folders, files in os.walk('.'):
    print(currentpath, folders, files)

Das zeigt uns:

# currentpath  folders           files
.              ['pics', 'docs']  ['todo.txt']
./pics         []                []
./docs         []                ['doc1.odt']

In der ersten Zeile sehen wir also ., dass wir uns in einem Ordner befinden , dass er zwei Ordner enthält, nämlich picsund docs, und dass es eine Datei gibt, nämlich todo.txt. Sie müssen nichts tun, um in diese Ordner zurückzukehren, da es, wie Sie sehen, automatisch wiederholt wird und Sie nur die Dateien in beliebigen Unterordnern erhalten. Und alle Unterordner davon (obwohl wir diese im Beispiel nicht haben).

Wenn Sie nur alle Dateien durchlaufen möchten find -type f, können Sie Folgendes tun:

for currentpath, folders, files in os.walk('.'):
    for file in files:
        print(os.path.join(currentpath, file))

Dies gibt aus:

./todo.txt
./docs/doc1.odt
Luc
quelle
9

Die pathlibBibliothek eignet sich hervorragend zum Arbeiten mit Dateien. Sie können einen rekursiven Glob für ein PathObjekt wie dieses erstellen .

from pathlib import Path

for elem in Path('/path/to/my/files').rglob('*.*'):
    print(elem)
chorbs
quelle
6

Wenn Sie eine flache Liste aller Pfade unter einem bestimmten Verzeichnis wünschen (wie find .in der Shell):

   files = [ 
       os.path.join(parent, name)
       for (parent, subdirs, files) in os.walk(YOUR_DIRECTORY)
       for name in files + subdirs
   ]

Lassen Sie es weg, um nur vollständige Pfade zu Dateien unter dem Basisverzeichnis einzuschließen + subdirs.

Scott Smith
quelle
6
import glob
import os

root_dir = <root_dir_here>

for filename in glob.iglob(root_dir + '**/**', recursive=True):
    if os.path.isfile(filename):
        with open(filename,'r') as file:
            print(file.read())

**/**wird verwendet, um alle Dateien einschließlich rekursiv abzurufen directory.

if os.path.isfile(filename)wird verwendet, um zu überprüfen, ob eine filenameVariable ist fileoder directoryob es sich um eine Datei handelt, dann können wir diese Datei lesen. Hier drucke ich Datei.

Neeraj Sonaniya
quelle
6

Ich habe Folgendes als am einfachsten empfunden

from glob import glob
import os

files = [f for f in glob('rootdir/**', recursive=True) if os.path.isfile(f)]

Mit using werden glob('some/path/**', recursive=True)alle Dateien abgerufen, aber auch Verzeichnisnamen. Durch Hinzufügen der if os.path.isfile(f)Bedingung wird diese Liste nur für vorhandene Dateien gefiltert

Michael Silverstein
quelle
3

Verwenden Sie os.path.join(), um Ihre Pfade zu konstruieren - Es ist ordentlicher:

import os
import sys
rootdir = sys.argv[1]
for root, subFolders, files in os.walk(rootdir):
    for folder in subFolders:
        outfileName = os.path.join(root,folder,"py-outfile.txt")
        folderOut = open( outfileName, 'w' )
        print "outfileName is " + outfileName
        for file in files:
            filePath = os.path.join(root,file)
            toWrite = open( filePath).read()
            print "Writing '" + toWrite + "' to" + filePath
            folderOut.write( toWrite )
        folderOut.close()
Ghostdog74
quelle
Es sieht so aus, als ob dieser Code nur für Ordner mit 2 Ebenen (oder tiefer) funktioniert. Trotzdem bringt es mich näher.
Brock Woolf
1

os.walkführt standardmäßig einen rekursiven Spaziergang durch. Ausgehend von root ergibt sich für jedes Verzeichnis ein 3-Tupel (dirpath, dirnames, filenames).

from os import walk
from os.path import splitext, join

def select_files(root, files):
    """
    simple logic here to filter out interesting files
    .py files in this example
    """

    selected_files = []

    for file in files:
        #do concatenation here to get full path 
        full_path = join(root, file)
        ext = splitext(file)[1]

        if ext == ".py":
            selected_files.append(full_path)

    return selected_files

def build_recursive_dir_tree(path):
    """
    path    -    where to begin folder scan
    """
    selected_files = []

    for root, dirs, files in walk(path):
        selected_files += select_files(root, files)

    return selected_files
b1r3k
quelle
1
Geben walk() Sie in Python 2.6 eine rekursive Liste zurück. Ich habe Ihren Code ausprobiert und eine Liste mit vielen Wiederholungen erhalten ... Wenn Sie nur Zeilen unter dem Kommentar "# rekursive Aufrufe von Unterordnern" entfernen - es funktioniert gut
borisbn
1

Versuche dies:

import os
import sys

for root, subdirs, files in os.walk(path):

    for file in os.listdir(root):

        filePath = os.path.join(root, file)

        if os.path.isdir(filePath):
            pass

        else:
            f = open (filePath, 'r')
            # Do Stuff
Diego
quelle
Warum sollten Sie ein anderes listdir () und dann isdir () ausführen, wenn Sie die Verzeichnisliste bereits von walk () in Dateien und Verzeichnisse aufgeteilt haben? Dies sieht so aus, als wäre es in großen Bäumen ziemlich langsam (führen Sie drei Syscalls anstelle von einem durch: 1 = walk, 2 = listdir, 3 = isdir, anstatt nur die 'subdirs' und 'files' zu durchlaufen und zu durchlaufen).
Luc
0

Ich denke, das Problem ist, dass Sie die Ausgabe von nicht os.walkrichtig verarbeiten.

Ändern Sie zunächst:

filePath = rootdir + '/' + file

zu:

filePath = root + '/' + file

rootdirist Ihr festes Startverzeichnis; rootist ein Verzeichnis, das von zurückgegeben wird os.walk.

Zweitens müssen Sie Ihre Dateiverarbeitungsschleife nicht einrücken, da es keinen Sinn macht, diese für jedes Unterverzeichnis auszuführen. Sie werden rootauf jedes Unterverzeichnis eingestellt. Sie müssen die Unterverzeichnisse nicht manuell verarbeiten, es sei denn, Sie möchten etwas mit den Verzeichnissen selbst tun.

Dave Webb
quelle
Ich habe Daten in jedem Unterverzeichnis, daher benötige ich eine separate Textdatei für den Inhalt jedes Verzeichnisses.
Brock Woolf
@Brock: Der Dateiteil ist die Liste der Dateien im aktuellen Verzeichnis. Die Einrückung ist also in der Tat falsch. Sie schreiben in filePath = rootdir + '/' + file, das klingt nicht richtig: Datei ist aus der Liste der aktuellen Dateien, also schreiben Sie in viele vorhandene Dateien?
Alok Singhal