El subproceso de Python lee la salida estándar mientras se ejecuta

Salman Mehmood 8 octubre 2023
  1. El subproceso de Python lee stdout mientras se ejecuta
  2. Enfoque 1: use check_call para leer stdout de un subprocess mientras se ejecuta en Python
  3. Enfoque 2: sondee el proceso para leer stdout de un “subproceso” mientras se ejecuta en Python
El subproceso de Python lee la salida estándar mientras se ejecuta

El objetivo principal de este artículo es demostrar cómo leer el stdout de un subprocess que se está ejecutando en Python.

El subproceso de Python lee stdout mientras se ejecuta

Al igual que con muchos otros módulos integrados, Subprocess también es un módulo integrado que viene preinstalado con una instalación de Python “normal”.

Se usa principalmente cuando desea ejecutar tareas, procesos y programas en un nuevo proceso, realizar un conjunto específico de tareas y devolver el resultado.

Una de las muchas razones de su amplio uso es que permite la ejecución de programas externos y ejecutables directamente desde el programa como un proceso separado.

Al ejecutar un programa utilizando la biblioteca subprocess, puede ser necesario que la salida de ese programa externo se muestre en tiempo real. Esto puede ser un requisito por muchas razones, como cuando un programa puede ser en tiempo real y depende de los cálculos después de un corto período de tiempo.

Considere el siguiente programa:

import subprocess


def execute(command):
    process = subprocess.Popen(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
    )
    output = process.communicate()[0]
    exitCode = process.returncode

    if exitCode == 0:
        return output
    else:
        raise Exception(command, exitCode, output)


if __name__ == "__main__":
    print(execute("cd C:\\ && C: && tree").decode("unicode_escape"))

Producción :

Folder PATH listing
Volume serial number is 0213-B7F2
C:.
+---DRIVERS
¦   +---bluetooth
¦       +---U1BLT07AVVASDSAP
¦           +---Custom
¦           ¦   +---EULA
¦           +---Win64
¦               +---LD
¦               +---svcpack
+---flutter
¦   +---.git
¦   ¦   +---hooks
¦   ¦   +---info
¦   ¦   +---logs
¦   ¦   ¦   +---refs
¦   ¦   ¦       +---heads
¦   ¦   ¦       +---remotes
¦   ¦   ¦           +---origin
¦   ¦   +---objects
¦   ¦   ¦   +---info
¦   ¦   ¦   +---pack
¦   ¦   +---refs
¦   ¦       +---heads
¦   ¦       +---remotes
¦   ¦       ¦   +---origin
¦   ¦       +---tags
¦   +---.github
¦   ¦   +---ISSUE_TEMPLATE
¦   ¦   +---workflows
¦   +---.idea
¦   ¦   +---runConfigurations
...

Podemos ver la salida en el programa pero no en tiempo real. La salida solo se mostrará después de que todo el comando (árbol en nuestro caso) haya terminado de ejecutarse.

La salida no mostrará nada hasta que el programa (o comando) se ejecute en un proceso separado usando el comando subprocess.

En nuestro caso, dado que se requiere que obtengamos la salida en tiempo real, necesitamos crear otra solución, la que muestra la salida del programa tal como está escrita en el stdout.

La solución se puede abordar de múltiples maneras, algunas de las cuales se mencionan a continuación.

Enfoque 1: use check_call para leer stdout de un subprocess mientras se ejecuta en Python

Considere el siguiente código:

import subprocess
import sys


def execute(command):
    subprocess.check_call(
        command, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT
    )


if __name__ == "__main__":
    print(execute("cd C:\\ && C: && tree").decode("unicode_escape"))

Producción :

Folder PATH listing
Volume serial number is 0213-B7F2
C:.
├───DRIVERS
│   └───bluetooth
│       └───U1BLT0720US14CMP
│           ├───Custom
│           │   └───EULA
│           └───Win64
│               ├───LD
│               └───svcpack
├───flutter
│   ├───.git
│   │   ├───hooks
│   │   ├───info
│   │   ├───logs
│   │   │   └───refs
│   │   │       ├───heads
│   │   │       └───remotes
│   │   │           └───origin
│   │   ├───objects
│   │   │   ├───info
│   │   │   └───pack
│   │   └───refs
│   │       ├───heads
│   │       ├───remotes
│   │       │   └───origin
│   │       └───tags
│   ├───.github
│   │   ├───ISSUE_TEMPLATE
│   │   └───workflows
│   ├───.idea
│   │   └───runConfigurations
│   ├───.pub-cache
│   │   ├───hosted
│   │   │   └───pub.dartlang.org
│   │   │       ├───.cache
...

Si el requisito principal es imprimir la salida del programa en tiempo real, se puede usar check_call para lograrlo. Esta solución simple, limpia y elegante es concisa y “simplemente perfecta” para programas muy simples, como cuando solo se necesita imprimir la salida.

El check_call también admite el paso de parámetros, por lo que si su programa necesita argumentos para funcionar, se pueden pasar fácilmente a la función sin problemas. Este método espera a que el programa se complete.

Según la finalización del programa, el método regresa; de lo contrario, genera una excepción CalledProcessError. El CalledProcessError tendrá el código de retorno de falla, al que se puede acceder usando el atributo returncode.

En el código mencionado anteriormente, el comando se pasa al método check_call, que contiene el comando (o el programa) a ejecutar.

El parámetro shell se configuró en true para ejecutar el proceso usando un shell, y la stdout del proceso se configuró en la stdout de nuestro programa, por lo que escribe directamente en nuestra stdout, y podemos ver los cambios a medida que ocurren en nuestro stdout.

Por último, el stderr se establece en el stdout del proceso generado.

Enfoque 2: sondee el proceso para leer stdout de un “subproceso” mientras se ejecuta en Python

Considere el siguiente código:

import subprocess
import sys


def execute(command):
    process = subprocess.Popen(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
    )

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline().decode("unicode_escape")
        if nextline == "" and process.poll() is not None:
            break
            sys.stdout.write(nextline)
            sys.stdout.flush()

        output = process.communicate()[0]
        exitCode = process.returncode

        if exitCode == 0:
            return output
        else:
            raise Exception(command, exitCode, output)


if __name__ == "__main__":
    print(execute("cd C:\\ && C: && tree").decode("unicode_escape"))

Producción :

...
¦   ¦   ¦       ¦   ¦       +---UuidUtil
¦   ¦   ¦       ¦   +---example
¦   ¦   ¦       ¦   +---lib
¦   ¦   ¦       ¦   +---test
¦   ¦   ¦       +---vector_math-2.1.2
¦   ¦   ¦       ¦   +---benchmark
¦   ¦   ¦       ¦   +---bin
¦   ¦   ¦       ¦   +---lib
¦   ¦   ¦       ¦   ¦   +---src
¦   ¦   ¦       ¦   ¦       +---vector_math
¦   ¦   ¦       ¦   ¦       ¦   +---third_party
¦   ¦   ¦       ¦   ¦       +---vector_math_64
¦   ¦   ¦       ¦   ¦       ¦   +---third_party
¦   ¦   ¦       ¦   ¦       +---vector_math_geometry
¦   ¦   ¦       ¦   ¦       ¦   +---filters
¦   ¦   ¦       ¦   ¦       ¦   +---generators
¦   ¦   ¦       ¦   ¦       +---vector_math_lists
¦   ¦   ¦       ¦   ¦       +---vector_math_operations
¦   ¦   ¦       ¦   +---test
¦   ¦   ¦       ¦   +---tool
¦   ¦   ¦       +---video_player-2.2.11
¦   ¦   ¦       ¦   +---android
¦   ¦   ¦       ¦   ¦   +---gradle
...

Para asegurarnos de que la salida del programa generada usando el subprocess se imprima tan pronto como se escriba en la stdout, debemos sondear el proceso para ver la salida y continuar leyendo la última línea de la stdout de la stdout del programa.

En un ciclo “infinito”, continuamos leyendo la salida del proceso generado usando readline(). Dado que la salida está codificada, necesitamos decodificarla (utf-escape en nuestro caso) para que se represente adecuadamente.

Esto se puede hacer utilizando el método decode y pasando el esquema de codificación correspondiente.

Una vez que el programa ha terminado de ejecutarse, también debemos escapar del bucle “infinito”. Podemos hacerlo comprobando dos cosas:

  1. La línea stdout actual está vacía.
  2. El proceso ha terminado o muerto.

Esto se puede hacer de forma bastante sencilla, con una simple comparación de cadenas para la primera condición y el método poll() para comprobar si el proceso ha terminado o no. Si el programa ha terminado, devuelve el returncode; en caso contrario, devuelve Ninguno.

Una simple comparación con Ninguno también nos puede dar información de si el programa ha terminado de ejecutarse o no.

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Artículo relacionado - Python Subprocess