Lectura de carpeta recursiva de Python
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()
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 enroot
el directorio de tipofiles
: Archivos enroot
(no ensubdirs
) de tipo distinto al directorio
¡Y úselo os.path.join
en lugar de concatenar con una barra diagonal! Su problema es filePath = rootdir + '/' + file
que 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 with
declaració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()
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)
De acuerdo con Dave Webb, os.walk
generará 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())
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.walk
es aproximadamente 3 veces más rápidoos.walk
Usé un poco más de memoria en mi prueba porque lafiles
matriz contenía 82k entradas mientras queglob
devuelve 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
i
inglob.iglob()
, devolverá una lista en lugar de un iterador y potencialmente usará mucha más memoria.
- Si olvida el
os.walk
no dará silenciosamente resultados incompletos ni interpretará inesperadamente un nombre como un patrón coincidenteglob
no muestra directorios vacíosglob
necesita escapar de los nombres de directorios y archivosglob.escape(name)
porque pueden contener caracteres especialesglob
excluye directorios y archivos que comienzan con un punto (p. ej.,~/.bashrc
o~/.vim
) yinclude_hidden
no resuelve eso (solo incluye carpetas ocultas ; debe especificar un segundo patrón para los archivos de puntos)glob
no te dice qué es un archivo y qué directorioglob
ingresa 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.walk
tienefollowlinks=True
como opción)os.walk
le 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 currentpath
la carpeta actual que está mirando. Esto generará:
.
./docs
./pics
Entonces se repite tres veces, porque hay tres carpetas: la actual, docs
y pics
. En cada bucle, llena las variables folders
y files
todas 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, pics
y 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