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()
Brock Woolf
fuente

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()
AndiDog
fuente
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)
ChillarAnand
fuente
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())
Clemente
fuente
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
Luc
fuente
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)
chorbs
fuente
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.

Scott Smith
fuente
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.

Neeraj Sonaniya
fuente
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

Michael Silverstein
fuente
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()
ghostdog74
fuente
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
b1r3k
fuente
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
Diego
fuente
¿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í.

Dave Webb
fuente
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