Curso Python DGA 2011/sistemas/procesos

De WikiEducator
Saltar a: navegación, buscar

Procesos en Python

En esta sección veremos cómo usar el módulo subprocess para poder lanzar procesos desde python, y conectar con sus entradas y salidas estándar y de error, así como obtener sus valores de retorno.

Unifica los módulos y funciones existentes para el control de procesos. Ventajas y motivaciones del módulo:

  • Reemplazar y unificar los variados módulos y funciones existentes, tales como os.system, os.spawn*, os.popen*, popen2.*, y commands.*.
  • Excepciones inter-proceso: Si se produce una excepción en el proceso hijo antes de ejecutar el comando, esta excepción se relanza al proceso padre. Ej: OSError cuando se intenta ejecutar un fichero que no existe
  • Posibilidad de ejecutar un hook entre fork() y exec()
  • No se llama implícitamente a la shell
  • Único interfaz para todas las posibles combinaciones de redirección de stdin, stdout, y stderr (en lugar de tener que usar multiples funciones, como popen2, popen3, etc
  • Soporte para conectar varios subprocesos (shell pipe)
  • Método comunicate para facilitar el envío de datos por el stdin y leer de stdout y stderr con seguridad de no caer en deadlocks.


subprocess define la clase 'Popen':

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)

Argumentos y ejecución en shell o fuera de ella

  • args debe ser una string describiendo el comando a ejecutar, o una lista de cadenas empezando por el comando y seguido por los parámetros a ejecutar, segun el caso:
  • En unix:
    • Si 'shell=False' (por defecto), args ha de ser una lista. Si se pasa una string, ha de ser únicamente el path al comando a ejecutar
    • Si 'shell=True', args ha de ser una cadena que será pasada tal cual a una shell.



Icon casestudy.gif

Comparando ejecución en shell y fuera de ella

>>> subprocess.Popen("ls -la .vim*", shell=True)
-rw------- 1 luis luis 26240 2011-08-29 09:18 .viminfo
-rw-r--r-- 1 luis luis   655 2011-08-05 08:37 .vimrc

Cuidado!

>>> subprocess.Popen("ls -la .vim*")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/subprocess.py", line 672, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1213, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

Fuera de la shell no se hacen sustituciones!:

>>> subprocess.Popen(["ls","-la",".vim*"])
<subprocess.Popen object at 0x2269c50>
>>> ls: cannot access .vim*: No such file or directory

Ok:

>>> subprocess.Popen(["ls","-la",".vimrc"])
-rw-r--r-- 1 luis luis 655 2011-08-05 08:37 .vimrc




  • Atención: Siempre que la ejecución de un comando vaya a hacerse como resultado de un input por parte de un usuario, ha de evitarse la ejecución en shell, puesto que es vulnerable a inyección de comandos.

Parámetros stdin, stdout y stderr

Sirven para poder redireccionar las entradas y salidas estandar y de error del nuevo proceso.

  • Estos parámetros admiten:
    • Un descriptor de archivo (entero positivo)
    • Un objeto de tipo file
    • subprocess.PIPE: Si se especifica esto, una nueva tubería o "pipe" será creada para conectar con el nuevo proceso.
    • None: No habrá ningún tipo de redirección



Icon casestudy.gif

Encadenando procesos con pipes

>>> p1 = Popen(["ps", "fax"], stdout=PIPE)
>>> p2 = Popen(["grep", "python"], stdin=p1.stdout)
>>> p1.stdout.close()  # Permitir que p1 reciba SIGPIPE si p2 acaba antes que p1
 4791 pts/3    S+     0:00              |           |   \_ python
 1869 ?        S      0:04              \_ /usr/bin/python /usr/share/system-con
 1558 ?        Sl     0:00 /usr/bin/python /usr/bin/zeitgeist-daemon
 1887 ?        Sl     0:48 /usr/bin/python /usr/lib/ubuntuone-client/ubuntuone-s
 2053 ?        S      0:00 /usr/bin/python /usr/lib/system-service/system-servic
 2057 ?        SNl    0:06 /usr/bin/python2.7 /usr/bin/update-manager --no-focus



Si se usa PIPE, los objetos tipo file que podemos usar para escribir se encuentran en Popen.stdin,Popen.stdout, y Popen.stderr. Atención: Para evitar deadlocks provocados por el bloqueo del proceso hijo por llenado de los buffers de las pipes, usar la función communicate() en lugar de las pipes directamente.

Otros parámetros

  • bufsize: Si se pasa, mismo significado que la función built-in open() de python: 0 - no se usa buffer. 1 - buffer por línea, y cualquier otro número positivo significa que se usará un buffer de ese tamaño. Un número negativo indica que se usará el valor por defecto del sistema operativo.
  • preexec_fn: Función que se ejecutará cuando se cree el proceso hijo, antes de la ejecución del programa o comando pasado (Solo en unix).
  • cwd: Path al que se cambiará el directorio actual de trabajo antes de ejecutar el proceso.
  • env: Map para sobreescribir las variables de entorno del sistema.

Obteniendo el código de retorno y el pid del proceso

  • El código de retorno se obtiene a través de Popen.returncode
  • El pid del nuevo proceso creado a través de Popen.pid

Otras funciones de subprocess para simplificar el uso

  • subprocess.call(*popenargs, **kwargs): Ejecuta el comando especificado en popenargs (mismo uso que en Popen), espera a la finalización del comando y devuelve el código de error. Se pueden pasar los mismos parámetros con nombre que en Popen.
  • subprocess.check_call(*popenargs, **kwargs): Igual que la anterior, pero lana una CalledProcessError si el valor de retorno es menor que 0. El objeto CalledProcessError contiene el código de retorno en el atributo returncode.
  • subprocess.check_output(*popenargs, **kwargs): Igual que la anterior, pero devuelve la salida como una cadena.
>>> subprocess.check_output(["ls", "-l", ".vimrc"])
'-rw-r--r-- 1 luis luis 655 2011-08-05 08:37 .vimrc\n'

Esperando a que acaben los procesos y comprobando estado

  • El método wait() de Popen bloquea hasta que el proceso acaba.
  • El método poll()' de Popen devuelve el código de retorno si el proceso ha acabado, o None si está en curso.

Terminando procesos

  • El método terminate() de Popen envía SIGTERM al proceso hijo en Unix. En Windows, ejecuta TerminateProcess() del api de win32.
  • El método kill() de Popen envíoa SIGKILL al proceso hijo en Unix. En Windows es un alias para terminate().



Icon casestudy.gif

Matando un proceso

Usando el método kill de Popen:

>>> p = Popen(["sleep", "1000"])
>>> p.kill()
>>> p.poll()
-9

Usando os.kill:

>>> p = Popen(["sleep", "1000"])
>>> p.pid
5162
>>> p.poll()
>>> 
>>> os.kill(5162, 9)
>>> p.poll()
-9




Enviando señales a procesos

  • Se pueden enviar señales a un proceso usando el método send_signal(signal).
  • También se puede usar el método kill(pid, signal) del módulo 'os'


Deadlocks y el método communicate

El uso de stdin y stdout/stderr mediante pipes en los procesos puede causar deadlocks si el proceso hijo genera suficientes datos en la pipe de salida tal que se bloquea esperando que el buffer acepte más datos. Para evitarlo, puede usarse Popen.communicate(input=None)

  • Interacciona con el proceso, envía datos al stdin y lee del stdout y stderr
  • Espera a que acabe el proceso
  • Se le puede enviar datos al proceso usando el parámetro input como una string
  • Devuelve una tupla (stdoutdata, stderrdata)
  • Si se quiere poder enviar al stdin y recibir del stdout y stderr, se ha de pasar PIPE al crear los objetos Popen.