Python: enumerar archivos en un directorio

P

Prefiero trabajar con Python porque es un lenguaje de programación muy flexible y me permite interactuar fácilmente con el sistema operativo. Esto también incluye funciones del sistema de archivos. Para simplemente enumerar archivos en un directorio, los módulos os, subprocess, fnmatchy pathlib ven a jugar. Las siguientes soluciones demuestran cómo utilizar estos métodos de forma eficaz.

Utilizando os.walk()

los os El módulo contiene una larga lista de métodos que tratan con el sistema de archivos y el sistema operativo. Uno de ellos es walk(), que genera los nombres de archivo en un árbol de directorios recorriendo el árbol de arriba hacia abajo o de abajo hacia arriba (siendo de arriba hacia abajo la configuración predeterminada).

os.walk() devuelve una lista de tres elementos. Contiene el nombre del directorio raíz, una lista de los nombres de los subdirectorios y una lista de los nombres de archivo en el directorio actual. El Listado 1 muestra cómo escribir esto con solo tres líneas de código. Esto funciona con intérpretes de Python 2 y 3.

Listado 1: Atravesando el directorio actual usando os.walk()

import os

for root, dirs, files in os.walk("."):
    for filename in files:
        print(filename)

Uso de la línea de comandos a través de subprocesos

Nota: Si bien esta es una forma válida de listar archivos en un directorio, no se recomienda ya que presenta la oportunidad de ataques de inyección de comandos.

Como ya se describió en el artículo Procesamiento paralelo en Python, el subprocess El módulo le permite ejecutar un comando del sistema y recopilar su resultado. El comando del sistema que llamamos en este caso es el siguiente:

Ejemplo 1: enumerar los archivos en el directorio actual

$ ls -p . | grep -v /$

El comando ls -p . enumera los archivos de directorio para el directorio actual y agrega el delimitador / al final del nombre de cada subdirectorio, que necesitaremos en el siguiente paso. La salida de esta llamada se canaliza al grep comando que filtra los datos según los necesitemos.

Los parametros -v /$ excluir todos los nombres de las entradas que terminan con el delimitador /. Realmente, /$ es una expresión regular que coincide con todas las cadenas que contienen el carácter / como el último carácter antes del final de la cadena, que está representado por $.

los subprocess El módulo permite construir tuberías reales y conectar los flujos de entrada y salida como lo hace en una línea de comando. Llamar al método subprocess.Popen() abre un proceso correspondiente y define los dos parámetros denominados stdin y stdout.

El Listado 2 muestra cómo programar eso. La primera variable ls se define como un proceso que ejecuta ls -p . que sale a una tubería. Es por eso que el canal stdout se define como subprocess.PIPE. La segunda variable grep también se define como un proceso, pero ejecuta el comando grep -v /$, en su lugar.

Para leer la salida del ls comando desde la tubería, el canal stdin de grep Se define como ls.stdout. Finalmente, la variable endOfPipe lee la salida de grep de grep.stdout que se imprime como elemento de salida estándar en el for-bucle debajo. La salida se ve en el Ejemplo 2.

Listado 2: Definición de dos procesos conectados con una tubería

import subprocess

# define the ls command
ls = subprocess.Popen(["ls", "-p", "."],
                      stdout=subprocess.PIPE,
                     )

# define the grep command
grep = subprocess.Popen(["grep", "-v", "/$"],
                        stdin=ls.stdout,
                        stdout=subprocess.PIPE,
                        )

# read from the end of the pipe (stdout)
endOfPipe = grep.stdout

# output the files line by line
for line in endOfPipe:
    print (line)

Ejemplo 2: ejecutar el programa

$ python find-files3.py
find-files2.py
find-files3.py
find-files4.py
...

Esta solución funciona bastante bien con Python 2 y 3, pero ¿podemos mejorarla de alguna manera? Entonces, echemos un vistazo a las otras variantes.

Combinatorio os y fnmatch

Como ha visto antes, la solución que usa subprocesos es elegante pero requiere mucho código. En cambio, combinemos los métodos de los dos módulos osy fnmatch. Esta variante también funciona con Python 2 y 3.

Como primer paso, importamos los dos módulos osy fnmatch. A continuación, definimos el directorio en el que nos gustaría listar los archivos usando os.listdir(), así como el patrón por qué archivos filtrar. en un for bucle iteramos sobre la lista de entradas almacenadas en la variable listOfFiles.

Finalmente, con la ayuda de fnmatch filtramos las entradas que estamos buscando e imprimimos las entradas coincidentes en stdout. El Listado 3 contiene el script Python y el Ejemplo 3 la salida correspondiente.

Listado 3: Listado de archivos usando os y el módulo fnmatch

import os, fnmatch

listOfFiles = os.listdir('.')
pattern = "*.py"
for entry in listOfFiles:
    if fnmatch.fnmatch(entry, pattern):
            print (entry)

Ejemplo 3: la salida del Listado 3

$ python2 find-files.py
find-files.py
find-files2.py
find-files3.py
...

Utilizando os.listdir() y generadores

En términos simples, un generador es un poderoso iterador que mantiene su estado. Para obtener más información sobre los generadores, consulte uno de nuestros artículos anteriores, Python Generators.

La siguiente variante combina el listdir() método del os módulo con función de generador. El código funciona con las versiones 2 y 3 de Python.

Como puede haber notado antes, el listdir() El método devuelve la lista de entradas para el directorio dado. El método os.path.isfile() devoluciones True si la entrada dada es un archivo. los yield El operador sale de la función pero mantiene el estado actual y devuelve solo el nombre de la entrada detectada como archivo. Esto nos permite recorrer la función del generador (ver Listado 4). La salida es idéntica a la del ejemplo 3.

Listado 4: Combinando os.listdir() y una función de generador

import os

def files(path):
    for file in os.listdir(path):
        if os.path.isfile(os.path.join(path, file)):
            yield file

for file in files("."):
    print (file)

Utilizar pathlib

los pathlib El módulo se describe a sí mismo como una forma de “analizar, compilar, probar y de otro modo trabajar en nombres de archivo y rutas utilizando una API orientada a objetos en lugar de operaciones de cadena de bajo nivel”. Esto suena genial, hagámoslo. A partir de Python 3, el módulo pertenece a la distribución estándar.

En el Listado 5, primero definimos el directorio. El punto (“.”) Define el directorio actual. A continuación, el iterdir() El método devuelve un iterador que produce los nombres de todos los archivos. en un for bucle imprimimos el nombre de los archivos uno tras otro.

Listado 5: Leyendo el contenido del directorio con pathlib

import pathlib

# define the path
currentDirectory = pathlib.Path('.')

for currentFile in currentDirectory.iterdir():
    print(currentFile)

Nuevamente, la salida es idéntica a la del ejemplo 3.

Como alternativa, podemos recuperar archivos haciendo coincidir sus nombres de archivo usando algo llamado glob. De esta forma solo podemos recuperar los archivos que queramos. Por ejemplo, en el código siguiente solo queremos enumerar los archivos de Python en nuestro directorio, lo que hacemos especificando “* .py” en el glob.

Listado 6: Usando pathlib con el glob método

import pathlib

# define the path
currentDirectory = pathlib.Path('.')

# define the pattern
currentPattern = "*.py"

for currentFile in currentDirectory.glob(currentPattern):
    print(currentFile)

Utilizando os.scandir()

En Python 3.6, un nuevo método está disponible en el os módulo. Se llama scandir()y simplifica significativamente la llamada a listar archivos en un directorio.

Habiendo importado el os módulo primero, use el getcwd() método para detectar el directorio de trabajo actual y guardar este valor en el path variable. Siguiente, scandir() devuelve una lista de entradas para esta ruta, que probamos para ser un archivo usando el is_file() método.

Listado 7: Leyendo el contenido del directorio con scandir()

import os

# detect the current working directory
path = os.getcwd()

# read the entries
with os.scandir(path) as listOfEntries:
    for entry in listOfEntries:
        # print all entries that are files
        if entry.is_file():
            print(entry.name)

Nuevamente, la salida del Listado 7 es idéntica a la del Ejemplo 3.

Conclusión

Hay desacuerdo sobre cuál es la mejor versión, cuál es la más elegante y cuál es la más “pythonica”. Me gusta la sencillez del os.walk() método, así como el uso de ambos fnmatch y pathlib módulos.

Las dos versiones con los procesos / tuberías y el iterador requieren una comprensión más profunda de los procesos de UNIX y el conocimiento de Python, por lo que pueden no ser las mejores para todos los programadores debido a su complejidad adicional (e innecesaria).

Para encontrar una respuesta a qué versión es la más rápida, el timeit El módulo es bastante útil. Este módulo cuenta el tiempo que ha transcurrido entre dos eventos.

Para comparar todas nuestras soluciones sin modificarlas, usamos una funcionalidad de Python: llame al intérprete de Python con el nombre del módulo y el código de Python apropiado que se ejecutará. Para hacer eso para todos los scripts de Python a la vez, un script de shell ayuda (Listado 8).

Listado 8: Evaluar el tiempo de ejecución usando el timeit módulo

#! /bin/bash

for filename in *.py; do
    echo "$filename:"
    cat $filename | python3 -m timeit
    echo " "
done

Las pruebas se realizaron con Python 3.5.3. El resultado es el siguiente, mientras que os.walk() da el mejor resultado. Ejecutar las pruebas con Python 2 devuelve valores diferentes pero no cambia el orden – os.walk() todavía está en la parte superior de la lista.

Resultado del método para 100,000,000 bucles

os.walk0.0085 usec por bucle
subproceso / tubería0.00859 usec por bucle
os.listdir / fnmatch0.00912 usec por bucle
os.listdir / generator0.00867 usec por bucle
pathlib0.00854 usec por bucle
pathlib / glob0.00858 usec por bucle
os.scandir0.00856 usec por bucle

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad