cómo obtener la salida estándar de subproceso en python al obtener SIGUSR2 / SIGINT

Tengo el siguiente script de python simple:

import os, subprocess,signal,sys import time out = None sub = None def handler(signum,frame): print("script.py: cached sig: %i " % signum) sys.stdout.flush() if sub is not None and not sub.poll(): print("render.py: sent signal to prman pid: ", sub.pid) sys.stdout.flush() sub.send_signal(signal.SIGTERM) sub.wait() # deadlocks....???? #os.kill(sub.pid, signal.SIGTERM) # this works #os.waitpid(sub.pid,0) # this works for i in range(0,5): time.sleep(0.1) print("script.py: cleanup %i" % i) sys.stdout.flush() sys.exit(128+signum) signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGUSR2, handler) signal.signal(signal.SIGTERM, handler) sub = subprocess.Popen(["./doStuff.sh"], stderr = subprocess.STDOUT) sub.wait() print("finished script.py") 

hacer cosas.sh

 #!/bin/bash function trap_with_arg() { func="$1" ; shift for sig ; do trap "$func $sig" "$sig" done } pid=False function signalHandler() { trap - SIGINT SIGTERM echo "doStuff.sh chached sig: $1" echo "doStuff.sh cleanup: wait 10s" sleep 10s # kill ourself to signal calling process we exited on SIGINT kill -s SIGINT $$ } trap_with_arg signalHandler SIGINT SIGTERM trap "echo 'doStuff.sh ignore SIGUSR2'" SIGUSR2 # ignore SIGUSR2 echo "doStuff.sh : pid: $$" echo "doStuff.sh: some stub error" 1>&2 for i in {1..100}; do sleep 1s echo "doStuff.sh, rendering $i" done 

cuando envío el proceso iniciado en un terminal mediante python3 scripts.py & una señal con kill -USR2 -$! el script captura el SIGINT y espera para siempre en sub.wait() , un ps -uf muestra lo siguiente:

 user 27515 0.0 0.0 29892 8952 pts/22 S 21:56 0:00 \_ python script.py user 27520 0.0 0.0 0 0 pts/22 Z 21:56 0:00 \_ [doStuff.sh]  

Tenga en cuenta que doStuff.sh maneja correctamente SIGINT y se cierra.

También me gustaría obtener la salida de stdout cuando se llama el handler ? ¿Cómo hacer esto correctamente?

¡Muchas gracias!

Su código no puede obtener la salida estándar del proceso hijo porque no redirige sus secuencias estándar al llamar a subprocess.Popen() . Es demasiado tarde para hacer algo al respecto en el controlador de señales.

Si desea capturar stdout, pase stdout=subprocess.PIPE y llame a .communicate() lugar de .wait() :

 child = subprocess.Popen(command, stdout=subprocess.PIPE) output = child.communicate()[0] 

Hay un problema completamente separado de que el manejador de señales se cuelga en la llamada .wait() en Python 3 (Python 2 u os.waitpid() no se cuelga aquí, sino que se recibe un estado de salida incorrecto del niño). Aquí hay un ejemplo de código mínimo para reproducir el problema :

 #!/usr/bin/env python import signal import subprocess import sys def sighandler(*args): child.send_signal(signal.SIGINT) child.wait() # It hangs on Python 3 due to child._waitpid_lock signal.signal(signal.SIGUSR1, sighandler) child = subprocess.Popen([sys.executable, 'child.py']) sys.exit("From parent %d" % child.wait()) # return child's exit status 

donde child.py :

 #!/usr/bin/env python """Called from parent.py""" import sys import time try: while True: time.sleep(1) except KeyboardInterrupt: # handle SIGINT sys.exit('child exits on KeyboardInterrupt') 

Ejemplo:

 $ python3 parent.py & $ kill -USR1 $! child exits on KeyboardInterrupt $ fg ... running python3 parent.py 

El ejemplo muestra que el hijo ha salido pero el padre todavía se está ejecutando. Si presionas Ctrl + C para interrumpirlo; el rastreo muestra que se cuelga with _self._waitpid_lock: sentencia dentro de la llamada .wait() . Si self._waitpid_lock = threading.Lock() se reemplaza por self._waitpid_lock = threading.RLock() en subprocess.py , el efecto es el mismo que usar os.waitpid() : no se bloquea, pero el estado de salida Es incorrecto.

Para evitar el problema, no espere el estado del niño en el controlador de señales: llame a send_signal() , establezca un indicador booleano simple y regrese desde el controlador de datos en su lugar. En el código principal, marque la bandera después de child.wait() (antes de print("finished script.py") en su código en la pregunta), para ver si la señal ha sido recibida (si no está clara desde child.returncode ). Si se establece la bandera; llame al código de limpieza apropiado y salga.

Debes mirar en subprocess.check_output

 proc_output = subprocess.check_output(commands_list, stderr=subprocess.STDOUT) 

Puedes rodearlo en un bash excepto y luego:

 except subprocess.CalledProcessError, error: create_log = u"Creation Failed with return code {return_code}\n{proc_output}".format( return_code=error.returncode, proc_output=error.output ) 

Solo puedo esperar al proceso usando

  os.kill(sub.pid, signal.SIGINT) os.waitpid(sub.pid,0) 

en lugar de

  sub.send_signal(signal.SIGINT) sub.wait() # blocks forever 

Esto tiene algo que ver con los grupos de procesos en UNIX, que realmente no entiendo: creo que el proceso ./doStuff.sh no recibe la señal porque los niños en los mismos grupos de procesos no reciben la señal. (No estoy seguro de si esto es correcto). Esperemos que alguien pueda dar más detalles sobre este tema.

La salida hasta que se llama al controlador se inserta en la salida estándar de la bash de llamada (consola).