Lettura cartella ricorsiva Python


225

Ho uno sfondo C ++ / Obj-C e sto solo scoprendo Python (lo sto scrivendo da circa un'ora). Sto scrivendo uno script per leggere ricorsivamente il contenuto dei file di testo in una struttura di cartelle.

Il problema che ho è che il codice che ho scritto funzionerà solo per una cartella profonda. Posso capire perché nel codice (vedi #hardcoded path), non so come posso andare avanti con Python poiché la mia esperienza con esso è solo nuova di zecca.

Codice Python:

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

Risposte:


347

Assicurati di comprendere i tre valori di ritorno di os.walk:

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

ha il seguente significato:

  • root: Percorso corrente "percorribile"
  • subdirs: File nella rootdirectory di tipo
  • files: File in root(non in subdirs) di tipo diverso dalla directory

E per favore usa os.path.joininvece di concatenare con una barra! Il tuo problema è filePath = rootdir + '/' + file: devi concatenare la cartella "walk" attualmente anziché la cartella più in alto. Così deve essere filePath = os.path.join(root, file). BTW "file" è un builtin, quindi normalmente non lo usi come nome di variabile.

Un altro problema sono i tuoi loop, che dovrebbero essere così, ad esempio:

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

Se non lo sapevi, l' withaffermazione per i file è una scorciatoia:

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

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()

4
Superba, molte stampe per capire cosa sta succedendo e funziona perfettamente. Grazie! +1
Brock Woolf,

16
Dirigi a chiunque sia stupido / ignaro come me ... questo esempio di codice scrive un file txt in ogni directory. Sono contento di averlo provato in una cartella controllata dalla versione, anche se tutto ciò di cui ho bisogno per scrivere uno script di cleanup è qui :)
Steazy

quel secondo (più lungo) frammento di codice ha funzionato molto bene, mi ha risparmiato un sacco di lavoro noioso
anfibio

1
Poiché la velocità è ovviamente l'aspetto più importante, os.walknon è male, anche se ho trovato un modo ancora più veloce os.scandir. Tutte le globsoluzioni sono molto più lente di walk& scandir. La mia funzione, così come una completa analisi di velocità, può essere trovato qui: stackoverflow.com/a/59803793/2441026
user136036

112

Se stai usando Python 3.5 o versioni successive, puoi farlo in 1 riga.

import glob

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

Come menzionato nella documentazione

Se ricorsivo è vero, il modello '**' corrisponderà a qualsiasi file e zero o più directory e sottodirectory.

Se vuoi ogni file, puoi usarlo

import glob

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

TypeError: iglob () ha ottenuto un argomento inaspettato per la parola chiave "ricorsivo"
Jewenile,

1
Come accennato all'inizio, è solo per Python 3.5+
ChillarAnand

9
root_dir deve avere una barra finale (altrimenti si ottiene qualcosa come 'cartella ** / *' invece di 'cartella / ** / *' come primo argomento). Puoi usare os.path.join (root_dir, ' * / '), ma non so se è accettabile usare os.path.join con percorsi jolly (funziona per la mia applicazione).
drojf

@ChillarAnand Puoi aggiungere un commento al codice in questa risposta che root_dirnecessita di una barra finale? Questo farà risparmiare tempo alle persone (o almeno mi farebbe risparmiare tempo). Grazie.
Dan Nissenbaum,

1
Se ho eseguito questo come nella risposta, non ha funzionato in modo ricorsivo. Per fare questo lavoro in modo ricorsivo ho dovuto cambiare a: glob.iglob(root_dir + '**/**', recursive=True). Sto lavorando in Python 3.8.2
Mike

38

D'accordo con Dave Webb, os.walkprodurrà un elemento per ogni directory nell'albero. Il fatto è che non devi preoccupartene subFolders.

Codice come questo dovrebbe funzionare:

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

3
Ben fatto. Funziona anche questo. Preferisco comunque la versione di AndiDog anche se è più lunga perché è più chiaro da capire come un principiante di Python. +1
Brock Woolf,

20

TL; DR: equivale a find -type fesaminare tutti i file in tutte le cartelle sottostanti e incluso quello corrente:

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

Come già menzionato in altre risposte, os.walk()è la risposta, ma potrebbe essere spiegata meglio. È abbastanza semplice! Camminiamo attraverso questo albero:

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

Con questo codice:

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

Il currentpathè la cartella corrente si sta guardando. Questo produrrà:

.
./docs
./pics

Quindi gira tre volte, perché ci sono tre cartelle: quella corrente docs, e pics. In ogni ciclo, riempie le variabili folderse filescon tutte le cartelle e i file. Mostriamo loro:

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

Questo ci mostra:

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

Quindi, nella prima riga, vediamo che siamo nella cartella ., che contiene due cartelle vale a dire picse docs, e che esiste un file, vale a dire todo.txt. Non devi fare nulla per ricorrere in quelle cartelle, perché come vedi, si ricorre automaticamente e ti dà solo i file in qualsiasi sottocartella. E qualsiasi sottocartella di questo (anche se non ne abbiamo quelli nell'esempio).

Se vuoi semplicemente scorrere tutti i file, l'equivalente di find -type f, puoi farlo:

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

Questo produce:

./todo.txt
./docs/doc1.odt

9

La pathliblibreria è davvero fantastica per lavorare con i file. Puoi fare un glob ricorsivo su un Pathoggetto in questo modo.

from pathlib import Path

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

6

Se si desidera un elenco semplice di tutti i percorsi in una determinata directory (come find .nella shell):

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

Per includere solo i percorsi completi dei file nella directory di base, lasciare fuori + subdirs.


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

**/**viene utilizzato per ottenere tutti i file in modo ricorsivo incluso directory.

if os.path.isfile(filename)è usato per verificare se la filenamevariabile è fileo directory, se è un file, allora possiamo leggere quel file. Qui sto stampando il file.


6

Ho trovato quanto segue per essere il più semplice

from glob import glob
import os

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

L'utilizzo glob('some/path/**', recursive=True)ottiene tutti i file, ma include anche i nomi delle directory. L'aggiunta della if os.path.isfile(f)condizione filtra questo elenco solo ai file esistenti


3

usa os.path.join()per costruire i tuoi percorsi - È più ordinato:

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

Sembra che questo codice funzioni solo per cartelle di 2 livelli (o più profonde). Comunque mi avvicina di più.
Brock Woolf,

1

os.walkcammina ricorsivamente per impostazione predefinita. Per ogni dir, a partire dalla radice produce una 3 tupla (dirpath, dirnames, nomi di file)

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

1
In Python 2.6 walk() do tornare lista ricorsiva. Ho provato il tuo codice e ho ottenuto un elenco con molte ripetizioni ... Se rimuovi solo le righe sotto il commento "# chiamate ricorsive su sottocartelle" - funziona benissimo
borisbn

1

Prova questo:

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

Perché dovresti fare un altro listdir () e poi isdir () quando hai già l'elenco delle directory diviso in file e directory da walk ()? Sembra che sarebbe piuttosto lento in alberi di grandi dimensioni (esegui tre syscalls invece di uno: 1 = walk, 2 = listdir, 3 = isdir, invece di camminare e scorrere tra i "sottodir" e "files").
Luc,

0

Penso che il problema sia che non stai elaborando os.walkcorrettamente l'output .

Innanzitutto, modifica:

filePath = rootdir + '/' + file

per:

filePath = root + '/' + file

rootdirè la tua directory di partenza fissa; rootè una directory restituita da os.walk.

In secondo luogo, non è necessario rientrare nel ciclo di elaborazione dei file, poiché non ha senso eseguirlo per ogni sottodirectory. Ti verrà rootimpostato su ogni sottodirectory. Non è necessario elaborare manualmente le sottodirectory a meno che non si desideri eseguire operazioni con le directory stesse.


Ho i dati in ogni sottodirectory, quindi ho bisogno di un file di testo separato per il contenuto di ogni directory.
Brock Woolf,

@Brock: la parte dei file è l'elenco dei file nella directory corrente. Quindi il rientro è davvero sbagliato. Stai scrivendo su filePath = rootdir + '/' + file, non suona bene: il file proviene dall'elenco dei file correnti, quindi stai scrivendo su molti file esistenti?
Alok Singhal,
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.