Lectura de carpeta recursiva de Python

Resuelto Brock Woolf asked hace 14 años • 17 respuestas

Tengo experiencia en C++/Obj-C y recién estoy descubriendo Python (lo he estado escribiendo durante aproximadamente una hora). Estoy escribiendo un script para leer recursivamente el contenido de archivos de texto en una estructura de carpetas.

El problema que tengo es que el código que he escrito solo funcionará en una carpeta de profundidad. Puedo ver por qué en el código (ver #hardcoded path), simplemente no sé cómo puedo avanzar con Python ya que mi experiencia con él es nueva.

Código 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()
Brock Woolf avatar Feb 06 '10 16:02 Brock Woolf
Aceptado

Asegúrese de comprender los tres valores de retorno de os.walk:

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

tiene el siguiente significado:

  • root: Camino actual "recorrido"
  • subdirs: Archivos en rootel directorio de tipo
  • files: Archivos en root(no en subdirs) de tipo distinto al directorio

¡Y úselo os.path.joinen lugar de concatenar con una barra diagonal! Su problema es filePath = rootdir + '/' + fileque debe concatenar la carpeta actualmente "recorrida" en lugar de la carpeta superior. Entonces eso debe serfilePath = os.path.join(root, file) . Por cierto, el "archivo" está incorporado, por lo que normalmente no lo usa como nombre de variable.

Otro problema son tus bucles, que deberían ser así, por ejemplo:

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 no lo sabías, la withdeclaración de archivos es una abreviatura:

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

# is effectively the same as

f = open('filename', 'rb')
try:
    dosomething()
finally:
    f.close()
AndiDog avatar Feb 06 '2010 09:02 AndiDog

Si está utilizando Python 3.5 o superior, puede hacerlo en 1 línea.

import glob

# root_dir needs a trailing slash (i.e. /root/dir/)
for filename in glob.iglob(root_dir + '**/*.txt', recursive=True):
     print(filename)

Como se menciona en la documentación .

Si recursivo es verdadero, el patrón '**' coincidirá con cualquier archivo y con cero o más directorios y subdirectorios.

Si quieres todos los archivos, puedes usar

import glob

for filename in glob.iglob(root_dir + '**/**', recursive=True):
     print(filename)
Chillar Anand avatar Jul 18 '2017 16:07 Chillar Anand

De acuerdo con Dave Webb, os.walkgenerará un elemento para cada directorio del árbol. El hecho es que simplemente no tienes por qué preocuparte subFolders.

Un código como este debería funcionar:

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 avatar Feb 06 '2010 09:02 Clément

TL;DR: Estos son equivalentes a find -type f, para revisar todos los archivos en todas las carpetas a continuación e incluyendo la actual:

folder = '.'

import os
for currentpath, folders, files in os.walk(folder):
    for file in files:
        print(os.path.join(currentpath, file))
## or:
import glob
for pathstr in glob.iglob(glob.escape(folder) + '/**/*', recursive=True):
    print(pathstr)

Comparando los dos métodos:

  • os.walkes aproximadamente 3 veces más rápido
  • os.walkUsé un poco más de memoria en mi prueba porque la filesmatriz contenía 82k entradas mientras que globdevuelve un iterador y transmite los resultados. El manejo gradual de cada resultado (más llamadas y menos almacenamiento en búfer) probablemente explica la diferencia de velocidad
    • Si olvida el iin glob.iglob(), devolverá una lista en lugar de un iterador y potencialmente usará mucha más memoria.
  • os.walkno dará silenciosamente resultados incompletos ni interpretará inesperadamente un nombre como un patrón coincidente
  • globno muestra directorios vacíos
  • globnecesita escapar de los nombres de directorios y archivos glob.escape(name)porque pueden contener caracteres especiales
  • globexcluye directorios y archivos que comienzan con un punto (p. ej., ~/.bashrco ~/.vim) y include_hiddenno resuelve eso (solo incluye carpetas ocultas ; debe especificar un segundo patrón para los archivos de puntos)
  • globno te dice qué es un archivo y qué directorio
  • globingresa a enlaces simbólicos y puede llevarlo a enumerar muchos archivos en lugares completamente diferentes (que puede ser lo que desea; en ese caso, os.walktiene followlinks=Truecomo opción)
  • os.walkle permite modificar qué rutas recorrer modificando la matriz de carpetas mientras se está ejecutando, aunque personalmente esto se siente un poco complicado y no estoy seguro de recomendarlo

Otras respuestas ya mencionadas os.walk(), pero podrían explicarse mejor. ¡Es bastante simple! Caminemos por este árbol:

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

Con este código:

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

Es currentpathla carpeta actual que está mirando. Esto generará:

.
./docs
./pics

Entonces se repite tres veces, porque hay tres carpetas: la actual, docsy pics. En cada bucle, llena las variables foldersy filestodas las carpetas y archivos. Mostrémosles:

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

Esto nos muestra:

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

Entonces, en la primera línea, vemos que estamos en la carpeta ., que contiene dos carpetas, a saber, picsy docs, y que hay un archivo, a saber todo.txt. No tiene que hacer nada para recurrir a esas carpetas porque, como puede ver, se repite automáticamente y solo le proporciona los archivos en cualquier subcarpeta. Y cualquier subcarpeta de eso (aunque no las tenemos en el ejemplo).

Si solo desea recorrer todos los archivos, el equivalente a find -type f, puede hacer esto:

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

Esto produce:

./todo.txt
./docs/doc1.odt
Luc avatar Jul 26 '2019 15:07 Luc