Cómo terminar un subproceso de Python lanzado con shell = True

308

Estoy iniciando un subproceso con el siguiente comando:

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

Sin embargo, cuando trato de matar usando:

p.terminate()

o

p.kill()

El comando sigue ejecutándose en segundo plano, así que me preguntaba cómo puedo realmente terminar el proceso.

Tenga en cuenta que cuando ejecuto el comando con:

p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)

Se termina con éxito al emitir el p.terminate().

usuario175259
fuente
¿Cómo es tu cmdaspecto? Puede contener un comando que desencadena varios procesos para iniciarse. Entonces no está claro de qué proceso hablas.
Robert Siemer
1
¿No haber shell=Truehecho una gran diferencia?
Charlie Parker

Respuestas:

410

Utilice un grupo de procesos para permitir el envío de una señal a todo el proceso en los grupos. Para eso, debe adjuntar una identificación de sesión al proceso primario de los procesos generados / secundarios, que es un shell en su caso. Esto lo convertirá en el líder del grupo de los procesos. Entonces, cuando se envía una señal al líder del grupo de procesos, se transmite a todos los procesos secundarios de este grupo.

Aquí está el código:

import os
import signal
import subprocess

# The os.setsid() is passed in the argument preexec_fn so
# it's run after the fork() and before  exec() to run the shell.
pro = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                       shell=True, preexec_fn=os.setsid) 

os.killpg(os.getpgid(pro.pid), signal.SIGTERM)  # Send the signal to all the process groups
mouad
fuente
2
@PiotrDobrogost: Lamentablemente no, porque os.setsidno está disponible en Windows ( docs.python.org/library/os.html#os.setsid ), no sé si esto puede ayudar, pero puede mirar aquí ( bugs.python.org / número5115 ) para obtener información sobre cómo hacerlo.
mouad
66
¿Cómo se subprocess.CREATE_NEW_PROCESS_GROUPrelaciona con esto?
Piotr Dobrogost
11
nuestras pruebas sugieren que setsid! = setpgid, y que os.pgkill solo mata los subprocesos que todavía tienen la misma identificación de grupo de proceso. procesos que tienen grupo de procesos modificados no se mató, a pesar de que todavía pueden tener el mismo identificador de sesión ...
hwjp
44
No recomendaría hacer os.setsid (), ya que también tiene otros efectos. Entre otros, desconecta el TTY controlador y convierte al nuevo proceso en un líder de grupo de procesos. Ver win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()termina matando el proceso de shell y cmdtodavía se está ejecutando.

Encontré una solución conveniente para esto:

p = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)

Esto hará que cmd herede el proceso de shell, en lugar de que el shell inicie un proceso hijo, que no se mata. p.pidserá la identificación de su proceso de cmd entonces.

p.kill() Deberia trabajar.

Sin embargo, no sé qué efecto tendrá esto en tu tubería.

Bryant Hansen
fuente
1
Solución agradable y ligera para * nix, ¡gracias! Funciona en Linux, también debería funcionar para Mac.
MarSoft
Muy buena solución. Si su cmd resulta ser un contenedor de script de shell para otra cosa, llame al binario final allí con exec también para tener un solo subproceso.
Nicolinux el
1
Esto es hermoso. He estado tratando de descubrir cómo generar y matar un subproceso por espacio de trabajo en Ubuntu. Esta respuesta me ayudó. Ojalá pudiera votarlo más de una vez
Sergiy Kolodyazhnyy
2
esto no funciona si se usa un punto y coma en el cmd
gnr
@speedyrazor: no funciona en Windows10. Creo que las respuestas específicas deberían estar claramente marcadas como tales.
DarkLight
48

Si puede usar psutil , entonces esto funciona perfectamente:

import subprocess

import psutil


def kill(proc_pid):
    process = psutil.Process(proc_pid)
    for proc in process.children(recursive=True):
        proc.kill()
    process.kill()


proc = subprocess.Popen(["infinite_app", "param"], shell=True)
try:
    proc.wait(timeout=3)
except subprocess.TimeoutExpired:
    kill(proc.pid)
Jovik
fuente
3
AttributeError: 'Process' object has no attribute 'get_childrenpara pip install psutil.
d33tah
3
Creo que get_children () debería ser children (). Pero no funcionó para mí en Windows, el proceso todavía está allí.
Godsmith
3
@Godsmith: la API de psutil ha cambiado y tienes razón: children () hace lo mismo que get_children (). Si no funciona en Windows, es posible que desee crear un ticket de error en GitHub
Jovik
24

Podría hacerlo usando

from subprocess import Popen

process = Popen(command, shell=True)
Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))

mató al cmd.exey al programa para el que le di la orden.

(En Windows)

SPratap
fuente
O use el nombre del proceso : Popen("TASKKILL /F /IM " + process_name)si no lo tiene, puede obtenerlo del commandparámetro.
zvi
12

Cuando shell=Trueel shell es el proceso hijo, y los comandos son sus hijos. Entonces cualquiera SIGTERMo SIGKILLmatará el shell pero no sus procesos hijos, y no recuerdo una buena manera de hacerlo. La mejor manera en que puedo pensar es en usar shell=False, de lo contrario, cuando elimine el proceso de shell principal, dejará un proceso de shell inactivo.

Sai Venkat
fuente
8

Ninguna de estas respuestas funcionó para mí, así que estoy dejando el código que funcionó. En mi caso, incluso después de finalizar el proceso .kill()y obtener un .poll()código de retorno, el proceso no finalizó.

Siguiendo la subprocess.Popen documentación :

"... con el fin de limpiar correctamente una aplicación con buen comportamiento debería matar el proceso secundario y finalizar la comunicación ..."

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

En mi caso, me faltaba el proc.communicate()después de llamar proc.kill(). Esto limpia el proceso stdin, stdout ... y termina el proceso.

epinal
fuente
Esta solución no me funciona en Linux y Python 2.7
user9869932
@xyz Me funcionó en Linux y python 3.5. Consulte los documentos para Python 2.7
epinal
@espinal, gracias, sí. Posiblemente sea un problema de Linux. Es Raspbian Linux ejecutándose en un Raspberry 3
user9869932
5

Como dijo Sai, el shell es el niño, por lo que las señales son interceptadas por él; la mejor manera que he encontrado es usar shell = False y usar shlex para dividir la línea de comando:

if isinstance(command, unicode):
    cmd = command.encode('utf8')
args = shlex.split(cmd)

p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Entonces p.kill () y p.terminate () deberían funcionar como espera.

Matt Billenstein
fuente
1
En mi caso, realmente no ayuda dado que cmd es "ruta de CD && zsync, etc, etc.". ¡Eso hace que el comando falle!
usuario175259
44
Utilice rutas absolutas en lugar de cambiar directorios ... Opcionalmente os.chdir (...) a ese directorio ...
Matt Billenstein
La capacidad de cambiar el directorio de trabajo para el proceso secundario está incorporada. Simplemente pasa el cwdargumento a Popen.
Jonathon Reinhart
Usé shlex, pero aún persiste el problema, matar no es matar los procesos secundarios.
hungryWolf
1

lo que siento que podríamos usar:

import os
import signal
import subprocess
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

os.killpg(os.getpgid(pro.pid), signal.SIGINT)

esto no matará toda su tarea sino el proceso con el p.pid

Nilesh K.
fuente
0

Sé que esta es una vieja pregunta, pero puede ayudar a alguien que busca un método diferente. Esto es lo que uso en Windows para eliminar los procesos que he llamado.

si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.call(["taskkill", "/IM", "robocopy.exe", "/T", "/F"], startupinfo=si)

/ IM es el nombre de la imagen, también puede hacer / PID si lo desea. / T mata tanto el proceso como los procesos secundarios. / F fuerza lo termina. si, como lo tengo configurado, es cómo hacer esto sin mostrar una ventana CMD. Este código se usa en python 3.

Zx4161
fuente
0

Enviar la señal a todos los procesos en grupo

    self.proc = Popen(commands, 
            stdout=PIPE, 
            stderr=STDOUT, 
            universal_newlines=True, 
            preexec_fn=os.setsid)

    os.killpg(os.getpgid(self.proc.pid), signal.SIGHUP)
    os.killpg(os.getpgid(self.proc.pid), signal.SIGTERM)
rogers.wang
fuente
0

No he visto esto mencionado aquí, así que lo estoy publicando en caso de que alguien lo necesite. Si todo lo que quiere hacer es asegurarse de que su subproceso finalice correctamente, puede ponerlo en un administrador de contexto. Por ejemplo, quería que mi impresora estándar imprimiera una imagen y al usar el administrador de contexto me aseguré de que el subproceso terminara.

import subprocess

with open(filename,'rb') as f:
    img=f.read()
with subprocess.Popen("/usr/bin/lpr", stdin=subprocess.PIPE) as lpr:
    lpr.stdin.write(img)
print('Printed image...')

Creo que este método también es multiplataforma.

Jaiyam Sharma
fuente
No veo cómo el código aquí termina un proceso. ¿Puedes aclarar?
DarkLight
He declarado claramente que si todo lo que desea hacer es asegurarse de que su proceso haya finalizado antes de pasar a la siguiente línea de su código, puede usar este método. No afirmé que termina el proceso.
Jaiyam Sharma el
Si el gestor de contexto "se asegura de que el proceso ha terminado", como usted ha dicho claramente, entonces haces afirmación de que termina el proceso. ¿Sin embargo? ¿Incluso cuando se inicia el proceso con shell=True, según la pregunta? Que no está en tu código.
anon
anon, gracias por señalar el uso confuso de la palabra 'terminado'. El administrador de contexto espera a que se complete el subproceso antes de pasar a la declaración de impresión en la última línea. Esto es diferente de terminar instantáneamente el proceso usando algo como un sigterm. Podría tener el mismo resultado usando un hilo y llamando thread.join(). Espero que esto lo aclare.
Jaiyam Sharma