El sistema operativo de Python y los subprocesos Popen Commands

Python ofrece varias opciones para ejecutar procesos externos e interactuar con el sistema operativo. Sin embargo, los métodos son diferentes para Python 2 y 3. Python 2...

Introducción

Python ofrece varias opciones para ejecutar procesos externos e interactuar con el sistema operativo. Sin embargo, los métodos son diferentes para Python 2 y 3. Python 2 tiene varios métodos en el módulo os, que ahora están obsoletos y reemplazados por el módulo subprocess, que es la opción preferida en Python 3.

A lo largo de este artículo, hablaremos sobre los diversos métodos os y subprocess, cómo usarlos, en qué se diferencian entre sí, en qué versión de Python se deben usar e incluso cómo convertirlos. los comandos más antiguos a los más nuevos.

Con suerte, al final de este artículo, comprenderá mejor cómo llamar a comandos externos desde el código de Python y qué método debe usar para hacerlo.

Primero están los métodos os.popen* más antiguos.

Los métodos os.popen*

El módulo os ofrece cuatro métodos diferentes que nos permiten interactuar con el sistema operativo (tal como lo haría con la línea de comandos) y crear un tubo) a otros comandos. Estos métodos a los que me refiero son: popen, popen2, popen3 y popen4, todos los cuales se describen en las siguientes secciones.

El objetivo de cada uno de estos métodos es poder llamar a otros programas desde su código Python. Esto podría ser llamar a otro ejecutable, como su propio programa C++ compilado, o un comando de shell como ls o mkdir.

os.popen

El método os.popen abre una tubería desde un comando. Esta canalización permite que el comando envíe su salida a otro comando. La salida es un archivo abierto al que pueden acceder otros programas.

La sintaxis es la siguiente:

1
os.popen(command[, mode[, bufsize]])

Aquí el parámetro command es lo que estarás ejecutando, y su salida estará disponible a través de un archivo abierto. El argumento mode define si este archivo de salida es o no legible ('r') o escribible ('w'). Agregar un 'b' al modo abrirá el archivo en modo binario. Así, por ejemplo "rb" producirá un objeto de archivo binario legible.

Para recuperar el código de salida del comando ejecutado, debe usar el método close() del objeto de archivo.

El parámetro bufsize le dice a popen cuántos datos almacenar en el búfer y puede asumir uno de los siguientes valores:

  • 0 = sin búfer (valor predeterminado)
  • 1 = línea almacenada en búfer
  • N = tamaño aproximado del búfer, cuando N > 0; y valor predeterminado, cuando N < 0

Este método está disponible para las plataformas Unix y Windows, y está obsoleto desde la versión 2.6 de Python. Si actualmente está usando este método y quiere cambiar a la versión de Python 3, aquí está la versión de subproceso equivalente para Python 3:

Método Reemplazado por


pipe = os.popen('cmd', 'r', bufsize) pipe = Popen('cmd', shell=True, bufsize=bufsize, stdout=PIPE).stdout pipe = os.popen('cmd', 'w', bufsize) pipe = Popen('cmd', shell=True, bufsize=bufsize, stdin=PIPE).stdin

El siguiente código muestra un ejemplo de cómo usar el método os.popen:

1
2
3
4
import os

p = os.popen('ls -la')
print(p.read())

El código anterior le pedirá al sistema operativo que enumere todos los archivos en el directorio actual. La salida de nuestro método, que se almacena en p, es un archivo abierto, que se lee e imprime en la última línea del código. El resultado de este código (en el contexto de mi directorio actual) es el siguiente:

1
2
3
4
5
6
7
8
9
$ python popen_test.py 
total 32
drwxr-xr-x   7 scott  staff  238 Nov  9 09:13 .
drwxr-xr-x  29 scott  staff  986 Nov  9 09:08 ..
-rw-r--r--   1 scott  staff   52 Nov  9 09:13 popen2_test.py
-rw-r--r--   1 scott  staff   55 Nov  9 09:14 popen3_test.py
-rw-r--r--   1 scott  staff   53 Nov  9 09:14 popen4_test.py
-rw-r--r--   1 scott  staff   49 Nov  9 09:13 popen_test.py
-rw-r--r--   1 scott  staff    0 Nov  9 09:13 subprocess_popen_test.py

os.popen2

Este método es muy similar al anterior. La principal diferencia es lo que genera el método. En este caso devuelve dos objetos de archivo, uno para la Entrada estándar y otro archivo para la [salida estándar](https:/ /en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29).

La sintaxis es la siguiente:

1
popen2(cmd[, mode[, bufsize]])

Estos argumentos tienen el mismo significado que en el método anterior, os.popen.

El método popen2 está disponible para las plataformas Unix y Windows. Sin embargo, solo se encuentra en Python 2. Nuevamente, si desea usar la versión subproceso en su lugar (que se muestra con más detalle a continuación), use lo siguiente en su lugar:


Método Reemplazado por


(child_stdin, child_stdout) = os.popen2('cmd', mode, bufsize) p = Popen('cmd', shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout) = (p.stdin, p.stdout)


El siguiente código muestra un ejemplo de cómo usar este método:

1
2
3
4
import os

in, out = os.popen2('ls -la')
print(out.read())

Este código producirá los mismos resultados que se muestran en la salida del primer código anterior. La diferencia aquí es que la salida del método popen2 consta de dos archivos. Por lo tanto, la segunda línea de código define dos variables: in y out. En la última línea, leemos el archivo de salida out y lo imprimimos en la consola.

os.popen3

Este método es muy similar a los anteriores. Sin embargo, la diferencia es que la salida del comando es un conjunto de tres archivos: stdin, stdout y estándar.

La sintaxis es:

1
os.popen3(cmd[, mode[, bufsize]])

donde los argumentos cmd, mode y bufsize tienen las mismas especificaciones que en los métodos anteriores. El método está disponible para plataformas Unix y Windows.

Tenga en cuenta que este método ha quedado obsoleto y la documentación de Python nos aconseja reemplazar el método popen3 de la siguiente manera:


Método Reemplazado por


(child_stdin,\ p = Popen('cmd', shell=True, bufsize=bufsize,
child_stdout,\ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
child_stderr) = os.popen3('cmd', modo, bufsize) (child_stdin,
child_stdout,
child_stderr) = (p.stdin, p.stdout, p.stderr)


Como en los ejemplos anteriores, el siguiente código producirá el mismo resultado que se ve en nuestro primer ejemplo.

1
2
3
4
import os

in, out, err = os.popen3('ls -la')
print(out.read())

Sin embargo, en este caso, tenemos que definir tres archivos: stdin, stdout y stderr. La lista de archivos de nuestro comando ls -la se guarda en el archivo out.

os.popen4

Como probablemente hayas adivinado, el método os.popen4 es similar a los métodos anteriores. Sin embargo, en este caso, solo devuelve dos archivos, uno para el stdin y otro para el stdout y el stderr.

Este método está disponible para las plataformas Unix y Windows y (¡sorpresa!) también está obsoleto desde la versión 2.6. Para reemplazarlo con la correspondiente llamada subproceso Popen, haga lo siguiente:


Método Reemplazado por


(child_stdin, child_stdout_and_stderr) = os.popen4('cmd', mode, bufsize) p = Popen('cmd', shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)


El siguiente código producirá el mismo resultado que en los ejemplos anteriores, que se muestra en el primer resultado de código anterior.

1
2
3
4
import os

in, out = os.popen4('ls -la')
print(we.read())

Como podemos ver en el código anterior, el método se parece mucho a popen2. Sin embargo, el archivo out del programa mostrará los resultados combinados de los flujos stdout y stderr.

Resumen de diferencias

Las diferencias entre los diferentes comandos popen* tienen que ver con su salida, que se resume en la siguiente tabla:

Argumentos del método


pope stdout entrada estándar popen2, salida estándar popen3 stdin, stdout, stderr popen4 stdin, stdout y stderr

Además, popen2, popen3 y popen4 solo están disponibles en Python 2 pero no en Python 3. Python 3 tiene disponible el método popen, pero se recomienda usar el módulo subprocess en su lugar, que describiremos con más detalle en la siguiente sección.

El método susbprocess.Popen

El módulo de subproceso fue creado con la intención de reemplazar varios métodos disponibles en el módulo os, que no se consideraban muy eficientes . Dentro de este módulo, encontramos la nueva clase Popen.

La documentación de Python recomienda el uso de Popen en casos avanzados, cuando otros métodos como subprocess.call no pueden satisfacer nuestras necesidades. Este método permite la ejecución de un programa como un proceso hijo. Debido a que el sistema operativo lo ejecuta como un proceso separado, los resultados dependen de la plataforma.

Los parámetros disponibles son los siguientes:

1
subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

Una diferencia principal de Popen es que es una clase y no solo un método. Por lo tanto, cuando llamamos a subprocess.Popen, en realidad estamos llamando al constructor de la clase Popen.

Hay bastantes argumentos en el constructor. El más importante de entender es args, que contiene el comando para el proceso que queremos ejecutar. Se puede especificar como una secuencia de parámetros (a través de una matriz) o como una sola cadena de comando.

El segundo argumento que es importante comprender es shell, cuyo valor predeterminado es False. En Unix, cuando necesitamos ejecutar un comando que pertenece al shell, como ls -la, debemos configurar shell=True.

Por ejemplo, el siguiente código llamará al comando de Unix ls -la a través de un shell.

1
2
import subprocess
subprocess.Popen('ls -la', shell=True)

Los resultados se pueden ver en la siguiente salida:

1
2
3
4
5
6
7
8
9
$ python subprocess_popen_test.py 
total 40
drwxr-xr-x   7 scott  staff  238 Nov  9 09:13 .
drwxr-xr-x  29 scott  staff  986 Nov  9 09:08 ..
-rw-r--r--   1 scott  staff   52 Nov  9 09:13 popen2_test.py
-rw-r--r--   1 scott  staff   55 Nov  9 09:14 popen3_test.py
-rw-r--r--   1 scott  staff   53 Nov  9 09:14 popen4_test.py
-rw-r--r--   1 scott  staff   49 Nov  9 09:13 popen_test.py
-rw-r--r--   1 scott  staff   56 Nov  9 09:16 subprocess_popen_test.py

Usando el siguiente ejemplo de una máquina con Windows, podemos ver las diferencias de usar el parámetro shell más fácilmente. Aquí estamos abriendo Microsoft Excel desde el shell o como un programa ejecutable. Desde la shell, es como si estuviéramos abriendo Excel desde una ventana de comandos.

El siguiente código abrirá Excel desde el shell (tenga en cuenta que tenemos que especificar shell=True):

1
2
import subprocess
subprocess.Popen("start excel", shell=True)

Sin embargo, podemos obtener los mismos resultados llamando al ejecutable de Excel. En este caso no estamos usando el shell, así que lo dejamos con su valor por defecto (False); pero tenemos que especificar la ruta completa al ejecutable.

1
2
import subprocess
subprocess.Popen("C:\Program Files (x86)\Microsoft Office\Office15\excel.exe")

Además, cuando instanciamos la clase Popen, tenemos acceso a varios métodos útiles:

Descripción del método


Popen.poll() Comprueba si el proceso secundario ha finalizado. Popen.wait() Espera a que termine el proceso hijo. Popen.communicate() Permite interactuar con el proceso. Popen.send_signal() Envía una señal al proceso hijo. Popen.terminate() Detiene el proceso hijo. Popen.kill() Elimina un proceso hijo.

La lista completa se puede encontrar en la documentación de subprocesos. El método más utilizado aquí es comunicar.

El método comunicar nos permite leer datos de la entrada estándar y también nos permite enviar datos a la salida estándar. Devuelve una tupla definida como (stdoutdata, stderrdata).

Por ejemplo, el siguiente código combinará los comandos dir y sort de Windows.

1
2
3
4
5
6
7
import subprocess

p1 = subprocess.Popen('dir', shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p2 = subprocess.Popen('sort /R', shell=True, stdin=p1.stdout)

p1.stdout.close()
out, err = p2.communicate() 

Para combinar ambos comandos, creamos dos subprocesos, uno para el comando dir y otro para el comando sort. Como queremos ordenar en orden inverso, agregamos la opción /R a la llamada sort.

Definimos la salida estándar del proceso 1 como PIPE, lo que nos permite usar la salida del proceso 1 como entrada para el proceso 2. Luego, debemos cerrar la salida estándar del proceso 1, para que pueda usarse como entrada del proceso 2. El la comunicación entre procesos se logra a través del método comunicar.

Ejecutar esto desde un shell de comandos de Windows produce lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> python subprocess_pipe_test.py
11/09/2017  08:52 PM                  234 subprocess_pipe_test.py
11/09/2017  07:13 PM                   99 subprocess_pipe_test2.py
11/09/2017  07:08 PM                   66 subprocess_pipe_test3.py
11/09/2017  07:01 PM                   56 subprocess_pipe_test4.py
11/09/2017  06:48 PM     <DIR>            ..
11/09/2017  06:48 PM     <DIR>            .
 Volume Serial Number is 2E4E-56A3
 Volume in drive D is ECA
 Directory of D:\MyPopen
               4 File(s)            455 bytes
               2 Dir(s)  18,634,326,016 bytes free

Terminando

Los métodos os presentaban una buena opción en el pasado, sin embargo, en la actualidad el módulo subprocess tiene varios métodos que son más potentes y eficientes de usar. Entre las herramientas disponibles está la clase Popen, que se puede utilizar en casos más complejos. Esta clase también contiene el método comunicar, que nos ayuda a unir diferentes comandos para una funcionalidad más compleja.

¿Para qué usas los métodos popen* y cuál prefieres? ¡Cuéntanos en los comentarios! ios!*