Lecture du dossier récursif Python


225

J'ai une formation en C ++ / Obj-C et je découvre juste Python (je l'écris depuis environ une heure). J'écris un script pour lire récursivement le contenu des fichiers texte dans une structure de dossiers.

Le problème que j'ai est que le code que j'ai écrit ne fonctionnera que pour un dossier en profondeur. Je peux voir pourquoi dans le code (voir #hardcoded path), je ne sais tout simplement pas comment je peux aller de l'avant avec Python car mon expérience avec celui-ci n'est que toute nouvelle.

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

Réponses:


347

Assurez-vous de bien comprendre les trois valeurs de retour de os.walk:

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

a la signification suivante:

  • root: Chemin actuel "parcouru"
  • subdirs: Fichiers dans le rootrépertoire type
  • files: Fichiers dans root(pas dans subdirs) d'un type autre que le répertoire

Et veuillez utiliser os.path.joinau lieu de concaténer avec une barre oblique! Votre problème est filePath = rootdir + '/' + file- vous devez concaténer le dossier actuellement "parcouru" au lieu du dossier le plus haut. Alors ça doit être filePath = os.path.join(root, file). BTW "fichier" est un programme intégré, donc vous ne l'utilisez normalement pas comme nom de variable.

Un autre problème est vos boucles, qui devraient être comme ceci, par exemple:

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

Si vous ne le saviez pas, l' withinstruction pour les fichiers est un raccourci:

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

# is effectively the same as

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

4
Superbe, beaucoup d'impressions pour comprendre ce qui se passe et cela fonctionne parfaitement. Merci! +1
Brock Woolf

16
Je m'adresse à n'importe qui aussi stupide / inconscient que moi ... cet exemple de code écrit un fichier txt dans chaque répertoire. Heureux de l'avoir testé dans un dossier contrôlé par version, bien que tout ce dont j'ai besoin pour écrire un script de nettoyage soit ici aussi :)
Steazy

ce deuxième extrait de code (le plus long) a très bien fonctionné, m'a sauvé beaucoup de travail ennuyeux
amphibient

1
Étant donné que la vitesse est évidemment l'aspect le plus important, ce os.walkn'est pas mauvais, même si j'ai trouvé un moyen encore plus rapide via os.scandir. Toutes les globsolutions sont beaucoup plus lentes que walk& scandir. Ma fonction, ainsi qu'une analyse complète de la vitesse, peuvent être trouvées ici: stackoverflow.com/a/59803793/2441026
user136036

112

Si vous utilisez Python 3.5 ou supérieur, vous pouvez le faire en 1 ligne.

import glob

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

Comme mentionné dans la documentation

Si récursif est vrai, le modèle «**» correspondra à tous les fichiers et à zéro ou plusieurs répertoires et sous-répertoires.

Si vous voulez chaque fichier, vous pouvez utiliser

import glob

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

TypeError: iglob () a obtenu un argument de mot clé inattendu 'récursif'
Jewenile

1
Comme mentionné au début, c'est uniquement pour Python 3.5+
ChillarAnand

9
root_dir doit avoir une barre oblique de fin (sinon vous obtenez quelque chose comme 'dossier ** / *' au lieu de 'dossier / ** / *' comme premier argument). Vous pouvez utiliser os.path.join (répertoire_racine, ' * / '), mais je ne sais pas s'il est acceptable d'utiliser os.path.join avec des chemins génériques (cela fonctionne cependant pour mon application).
drojf

@ChillarAnand Pouvez-vous s'il vous plaît ajouter un commentaire au code dans cette réponse qui a root_dirbesoin d'une barre oblique finale? Cela fera gagner du temps aux gens (ou du moins cela m'aurait fait gagner du temps). Merci.
Dan Nissenbaum

1
Si je l'ai exécuté comme dans la réponse, cela n'a pas fonctionné récursivement. Pour faire ce travail récursive que je devais changer à: glob.iglob(root_dir + '**/**', recursive=True). Je travaille en Python 3.8.2
Mikey

38

D'accord avec Dave Webb, os.walkproduira un élément pour chaque répertoire de l'arborescence. Le fait est que vous n'avez pas à vous soucier subFolders.

Un code comme celui-ci devrait fonctionner:

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
Joli. Cela fonctionne aussi. Je préfère cependant la version d'AndiDog même si elle est plus longue car elle est plus claire à comprendre en tant que débutant en Python. +1
Brock Woolf

20

TL; DR: C'est l'équivalent de find -type fparcourir tous les fichiers dans tous les dossiers ci-dessous et y compris celui en cours:

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

Comme déjà mentionné dans d'autres réponses, os.walk()c'est la réponse, mais elle pourrait être mieux expliquée. C'est assez simple! Parcourons cet arbre:

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

Avec ce code:

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

C'est currentpathle dossier actuel qu'il regarde. Cela produira:

.
./docs
./pics

Il boucle donc trois fois, car il existe trois dossiers: le dossier actuel,, docset pics. Dans chaque boucle, il remplit les variables folderset filesavec tous les dossiers et fichiers. Montrons-leur:

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

Cela nous montre:

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

Donc, dans la première ligne, nous voyons que nous sommes dans un dossier ., qu'il contient deux dossiers à savoir picset docs, et qu'il y a un fichier, à savoir todo.txt. Vous n'avez rien à faire pour récursivement dans ces dossiers, car comme vous le voyez, il se reproduit automatiquement et vous donne simplement les fichiers dans tous les sous-dossiers. Et tous les sous-dossiers de cela (bien que nous n'en ayons pas dans l'exemple).

Si vous voulez simplement parcourir tous les fichiers, l'équivalent de find -type f, vous pouvez le faire:

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

Cela produit:

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

9

La pathlibbibliothèque est vraiment idéale pour travailler avec des fichiers. Vous pouvez faire un glob récursif sur un Pathobjet comme ça.

from pathlib import Path

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

6

Si vous voulez une liste plate de tous les chemins sous un répertoire donné (comme find .dans le shell):

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

Pour inclure uniquement les chemins d'accès complets aux fichiers sous le répertoire de base, omettez + 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())

**/**est utilisé pour obtenir récursivement tous les fichiers, y compris directory.

if os.path.isfile(filename)est utilisé pour vérifier si la filenamevariable est fileou directory, s'il s'agit d'un fichier, nous pouvons lire ce fichier. Ici, j'imprime un fichier.


6

J'ai trouvé que ce qui suit était le plus simple

from glob import glob
import os

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

L'utilisation glob('some/path/**', recursive=True)obtient tous les fichiers, mais inclut également les noms de répertoire. L'ajout de la if os.path.isfile(f)condition filtre cette liste aux fichiers existants uniquement


3

utiliser os.path.join()pour construire vos chemins - C'est plus soigné:

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

Il semble que ce code ne fonctionne que pour les dossiers de 2 niveaux (ou plus profonds). Cela me rapproche encore.
Brock Woolf

1

os.walkfait une marche récursive par défaut. Pour chaque répertoire, à partir de la racine, il donne un triplet (répertoire, répertoires, noms de fichiers)

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
En Python 2.6 walk() do retour liste récursive. J'ai essayé votre code et j'ai obtenu une liste avec de nombreuses répétitions ... Si vous supprimez simplement les lignes sous le commentaire "# appels récursifs sur les sous-dossiers" - cela fonctionne très bien
borisbn

1

Essaye ça:

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

Pourquoi feriez-vous un autre listdir () puis isdir () alors que la liste des répertoires est déjà divisée en fichiers et répertoires à partir de walk ()? Il semble que ce soit plutôt lent dans les grands arbres (faites trois appels système au lieu d'un: 1 = marche, 2 = listdir, 3 = isdir, au lieu de simplement marcher et parcourir les `` sous-répertoires '' et les `` fichiers '').
Luc

0

Je pense que le problème est que vous ne traitez pas os.walkcorrectement la sortie de .

Tout d'abord, changez:

filePath = rootdir + '/' + file

à:

filePath = root + '/' + file

rootdirest votre répertoire de départ fixe; rootest un répertoire renvoyé par os.walk.

Deuxièmement, vous n'avez pas besoin de mettre en retrait votre boucle de traitement de fichiers, car cela n'a aucun sens de l'exécuter pour chaque sous-répertoire. Vous serez rootconfiguré pour chaque sous-répertoire. Vous n'avez pas besoin de traiter les sous-répertoires à la main, sauf si vous voulez faire quelque chose avec les répertoires eux-mêmes.


J'ai des données dans chaque sous-répertoire, j'ai donc besoin d'avoir un fichier texte séparé pour le contenu de chaque répertoire.
Brock Woolf

@Brock: la partie fichiers est la liste des fichiers du répertoire courant. Donc, l'indentation est vraiment fausse. Vous écrivez filePath = rootdir + '/' + file, cela ne semble pas correct: le fichier provient de la liste des fichiers actuels, vous écrivez donc sur un grand nombre de fichiers existants?
Alok Singhal
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.