Python: enumerar archivos en un directorio

    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

     

    Rate this post
    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *