Lectura recursiva de Python


225

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

El problema que tengo es que el código que he escrito solo funcionará para 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 completamente nueva.

Código de 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()

Respuestas:


347

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: Ruta actual que se "recorrió"
  • subdirs: Archivos en el rootdirectorio de tipo
  • files: Archivos en root(no en subdirs) de otro tipo que no sea directorio

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

Otro problema son sus 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ía, la withdeclaración para los 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()

44
Excelente, muchas impresiones para entender lo que está sucediendo y funciona perfectamente. ¡Gracias! +1
Brock Woolf

16
Dirígete a cualquiera tan tonto / ajeno como yo ... este ejemplo de código escribe un archivo txt en cada directorio. Me alegro de haberlo probado en una carpeta de versión controlada, aunque todo lo que necesito para escribir un script de limpieza también está aquí :)
Steazy

la segunda (la más larga) fragmento de código funcionó muy bien, me salvó un montón de trabajo aburrido
amphibient

1
Dado que la velocidad es obviamente el aspecto más importante, os.walkno está mal, aunque se me ocurrió una forma aún más rápida os.scandir. Todas las globsoluciones son mucho más lentas que walk& scandir. Mi función, así como un análisis de velocidad completo, se puede encontrar aquí: stackoverflow.com/a/59803793/2441026
user136036

112

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

import glob

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

Como se menciona en la documentación

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

Si desea cada archivo, puede usar

import glob

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

TypeError: iglob () obtuvo un argumento de palabra clave inesperado 'recursivo'
Jewenile

1
Como se mencionó al principio, es solo para Python
3.5+

99
root_dir debe tener una barra diagonal final (de lo contrario, obtendrá algo como 'carpeta ** / *' en lugar de 'carpeta / ** / *' como primer argumento). Puede usar os.path.join (root_dir, ' * / '), pero no sé si es aceptable usar os.path.join con rutas comodín (aunque funciona para mi aplicación).
drojf

@ChillarAnand ¿Puede agregar un comentario al código en esta respuesta que root_dirnecesita una barra diagonal final? Esto ahorrará tiempo a la gente (o al menos me habría ahorrado tiempo). Gracias.
Dan Nissenbaum

1
Si ejecuté esto como en la respuesta, no funcionó recursivamente. Para hacer este trabajo de forma recursiva tuve que cambiarlo a: glob.iglob(root_dir + '**/**', recursive=True). Estoy trabajando en Python 3.8.2
mikey

38

De acuerdo con Dave Webb, os.walkproducirá un elemento para cada directorio en el árbol. El hecho es que no tienes que 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())

3
Buena esa. Esto también funciona. Sin embargo, prefiero la versión de AndiDog, aunque es más larga porque es más fácil de entender como principiante de Python. +1
Brock Woolf

20

TL; DR: Esto es equivalente a find -type frevisar todos los archivos en todas las carpetas a continuación e incluir la actual:

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

Como ya se mencionó en otras respuestas, os.walk()es la respuesta, pero podría explicarse mejor. ¡Es bastante simple! Pasemos por este árbol:

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

Con este código:

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

La currentpathes 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 foldersy filescon todas las carpetas y archivos. Vamos a mostrarles

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

Así, en la primera línea, vemos que estamos en la carpeta ., que contiene dos carpetas a saber picsy docs, y que hay un solo 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 da 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 de 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

9

La pathlibbiblioteca es realmente genial para trabajar con archivos. Puedes hacer un globo recursivo en un Pathobjeto así.

from pathlib import Path

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

6

Si desea una lista plana de todas las rutas bajo un directorio dado (como find .en el shell):

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

Para incluir solo rutas completas a los archivos bajo el directorio base, omita + 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())

**/**se utiliza para obtener todos los archivos de forma recursiva, incluidos directory.

if os.path.isfile(filename)se usa para verificar si la filenamevariable es fileo directory, si es un archivo, entonces podemos leer ese archivo. Aquí estoy imprimiendo el archivo.


6

He encontrado que lo siguiente es lo más fácil

from glob import glob
import os

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

El uso glob('some/path/**', recursive=True)obtiene todos los archivos, pero también incluye los nombres de directorio. Agregar la if os.path.isfile(f)condición filtra esta lista solo a los archivos existentes


3

use os.path.join()para construir sus caminos - Es más ordenado:

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

Parece que este código funciona solo para carpetas de 2 niveles (o más profundos). Aún así me acerca.
Brock Woolf

1

os.walkhace caminata recursiva por defecto. Para cada directorio, comenzando desde la raíz, produce una tupla de 3 (dirpath, dirnames, nombres de archivo)

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() , devuelve la lista recursiva. Probé su código y obtuve una lista con muchas repeticiones ... Si solo elimina las líneas debajo del comentario "# llamadas recursivas en subcarpetas" - funciona bien
borisbn

1

Prueba esto:

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

¿Por qué haría otro listdir () y luego isdir () cuando ya tiene la lista de directorios dividida en archivos y directorios de walk ()? Parece que sería bastante lento en árboles grandes (haga tres syscalls en lugar de uno: 1 = caminar, 2 = listdir, 3 = isdir, en lugar de simplemente caminar y recorrer los 'subdirs' y 'archivos').
Luc

0

Creo que el problema es que no estás procesando la salida os.walkcorrectamente.

En primer lugar, cambiar:

filePath = rootdir + '/' + file

a:

filePath = root + '/' + file

rootdires su directorio de inicio fijo; rootes un directorio devuelto por os.walk.

En segundo lugar, no necesita sangrar el bucle de procesamiento de archivos, ya que no tiene sentido ejecutar esto para cada subdirectorio. Se rootestablecerá en cada subdirectorio. No necesita procesar los subdirectorios a mano, a menos que quiera hacer algo con los directorios en sí.


Tengo datos en cada subdirectorio, por lo que necesito tener un archivo de texto separado para el contenido de cada directorio.
Brock Woolf

@Brock: la parte de archivos es la lista de archivos en el directorio actual. Entonces, la sangría es realmente incorrecta. Estás escribiendo filePath = rootdir + '/' + file, eso no suena bien: ¿el archivo es de la lista de archivos actuales, entonces estás escribiendo en muchos archivos existentes?
Alok Singhal
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.