Comment terminer un sous-processus python lancé avec shell = True

308

Je lance un sous-processus avec la commande suivante:

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

Cependant, lorsque j'essaie de tuer en utilisant:

p.terminate()

ou

p.kill()

La commande continue de s'exécuter en arrière-plan, alors je me demandais comment puis-je réellement mettre fin au processus.

Notez que lorsque j'exécute la commande avec:

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

Il se termine avec succès lors de l'émission du p.terminate().

user175259
la source
A quoi ressemble ton cmdlook? Il peut contenir une commande qui déclenche le démarrage de plusieurs processus. Il n'est donc pas clair de quel processus vous parlez.
Robert Siemer
1
cela n'a-t-il pas shell=Truefait une grande différence?
Charlie Parker

Réponses:

410

Utilisez un groupe de processus afin de permettre l'envoi d'un signal à tous les processus des groupes. Pour cela, vous devez attacher un identifiant de session au processus parent des processus générés / enfants, qui est un shell dans votre cas. Cela en fera le chef de groupe des processus. Alors maintenant, lorsqu'un signal est envoyé au leader du groupe de processus, il est transmis à tous les processus enfants de ce groupe.

Voici le code:

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
la source
2
@PiotrDobrogost: Malheureusement non, car il os.setsidn'est pas disponible dans Windows ( docs.python.org/library/os.html#os.setsid ), je ne sais pas si cela peut aider mais vous pouvez regarder ici ( bugs.python.org / issue5115 ) pour un aperçu de la façon de le faire.
mouad
6
Quel subprocess.CREATE_NEW_PROCESS_GROUPrapport avec cela?
Piotr Dobrogost
11
nos tests suggèrent que setsid! = setpgid, et que os.pgkill ne tue que les sous-processus qui ont toujours le même identifiant de groupe de processus. les processus qui ont changé de groupe de processus ne sont pas supprimés, même s'ils peuvent toujours avoir le même identifiant de session ...
hwjp
4
Je ne recommanderais pas de faire os.setsid (), car cela a aussi d'autres effets. Entre autres, il déconnecte l'ATS de contrôle et fait du nouveau processus un chef de groupe de processus. Voir win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()finit par tuer le processus shell et cmdest toujours en cours d'exécution.

J'ai trouvé un correctif pratique en:

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

Cela entraînera cmd à hériter du processus shell, au lieu d'avoir le shell lancer un processus enfant, qui ne sera pas tué. p.pidsera alors l'id de votre processus cmd.

p.kill() devrait marcher.

Je ne sais pas cependant quel effet cela aura sur votre pipe.

Bryant Hansen
la source
1
Solution agréable et légère pour * nix, merci! Fonctionne sous Linux, devrait également fonctionner pour Mac.
MarSoft
Très belle solution. Si votre cmd se trouve être un wrapper de script shell pour autre chose, appelez-y également le binaire final avec exec afin de n'avoir qu'un seul sous-processus.
Nicolinux
1
C'est beau. J'ai essayé de comprendre comment générer et tuer un sous-processus par espace de travail sur Ubuntu. Cette réponse m'a aidé. J'aimerais pouvoir voter plus d'une fois
Sergiy Kolodyazhnyy
2
cela ne fonctionne pas si un point-virgule est utilisé dans le cmd
gnr
@speedyrazor - Ne fonctionne pas sur Windows10. Je pense que les réponses spécifiques doivent être clairement marquées comme telles.
DarkLight
48

Si vous pouvez utiliser psutil , cela fonctionne parfaitement:

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
la source
3
AttributeError: 'Process' object has no attribute 'get_childrenpour pip install psutil.
d33tah
3
Je pense que get_children () devrait être des enfants (). Mais cela n'a pas fonctionné pour moi sur Windows, le processus est toujours là.
Godsmith
3
@Godsmith - L'API psutil a changé et vous avez raison: children () fait la même chose que get_children (). Si cela ne fonctionne pas sous Windows, vous pouvez créer un ticket de bogue dans GitHub
Jovik
24

Je pourrais le faire en utilisant

from subprocess import Popen

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

il a tué le cmd.exeet le programme pour lequel j'ai donné la commande.

(Sous Windows)

SPratap
la source
Ou utilisez le nom du processus : Popen("TASKKILL /F /IM " + process_name)si vous ne l'avez pas, vous pouvez l'obtenir à partir du commandparamètre.
zvi
12

Lorsque shell=Truele shell est le processus enfant et que les commandes sont ses enfants. Donc, tout SIGTERMou SIGKILLva tuer le shell mais pas ses processus enfants, et je ne me souviens pas d'une bonne façon de le faire. La meilleure façon que je puisse penser est d'utiliser shell=False, sinon lorsque vous tuez le processus shell parent, il laissera un processus shell défunt.

Sai Venkat
la source
8

Aucune de ces réponses n'a fonctionné pour moi, donc je laisse le code qui a fonctionné. Dans mon cas, même après avoir tué le processus avec .kill()et obtenu un .poll()code retour, le processus ne s'est pas terminé.

Suite à la subprocess.Popen documentation :

"... afin de nettoyer correctement une application bien tenue devrait tuer le processus enfant et terminer la communication ..."

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

Dans mon cas, je manquais le proc.communicate()après avoir appelé proc.kill(). Cela nettoie le processus stdin, stdout ... et met fin au processus.

épinal
la source
Cette solution ne fonctionne pas pour moi sous linux et python 2.7
user9869932
@xyz Cela a fonctionné pour moi sous Linux et python 3.5. Consultez les documents pour python 2.7
epinal
@espinal, merci, oui. C'est peut-être un problème Linux. C'est Raspbian linux fonctionnant sur un Raspberry 3
user9869932
5

Comme l'a dit Sai, le shell est l'enfant, donc les signaux sont interceptés par lui - le meilleur moyen que j'ai trouvé est d'utiliser shell = False et d'utiliser shlex pour diviser la ligne de commande:

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

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

Ensuite, p.kill () et p.terminate () devraient fonctionner comme prévu.

Matt Billenstein
la source
1
Dans mon cas, cela n'aide pas vraiment étant donné que cmd est "chemin cd && zsync etc etc". Cela fait donc échouer la commande!
user175259
4
Utilisez des chemins absolus au lieu de changer de répertoire ... Facultativement os.chdir (...) vers ce répertoire ...
Matt Billenstein
La possibilité de modifier le répertoire de travail du processus enfant est intégrée. Passez l' cwdargument à Popen.
Jonathon Reinhart
J'ai utilisé shlex, mais le problème persiste, tuer ne tue pas les processus enfants.
hungryWolf
1

ce que je pense que nous pourrions utiliser:

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

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

cela ne tuera pas toute votre tâche mais le processus avec le p.pid

Nilesh K.
la source
0

Je sais que c'est une vieille question mais cela peut aider quelqu'un qui cherche une méthode différente. C'est ce que j'utilise sur Windows pour tuer les processus que j'ai appelés.

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

/ IM est le nom de l'image, vous pouvez également faire / PID si vous le souhaitez. / T tue le processus ainsi que les processus enfants. / F force y met fin. si, comme je l'ai défini, est la façon dont vous faites cela sans afficher de fenêtre CMD. Ce code est utilisé en python 3.

Zx4161
la source
0

Envoyer le signal à tous les processus du groupe

    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
la source
0

Je n'ai pas vu cela mentionné ici, donc je le mets au cas où quelqu'un en aurait besoin. Si tout ce que vous voulez faire est de vous assurer que votre sous-processus se termine avec succès, vous pouvez le placer dans un gestionnaire de contexte. Par exemple, je voulais que mon imprimante standard imprime une image et que le gestionnaire de contexte garantisse la fin du sous-processus.

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...')

Je pense que cette méthode est également multiplateforme.

Jaiyam Sharma
la source
Je ne vois pas comment le code termine ici un processus. Pouvez-vous clarifier?
DarkLight
J'ai clairement indiqué que si tout ce que vous voulez faire s'assure que votre processus est terminé avant de passer à la ligne suivante de votre code, vous pouvez utiliser cette méthode. Je n'ai pas prétendu qu'il mettait fin au processus.
Jaiyam Sharma
Si le gestionnaire de contexte « veille à ce que votre processus a pris fin », comme vous l' avez dit clairement, alors vous faites affirmation selon laquelle il met fin au processus. Est-ce que c'est bien? Même lorsque le processus est lancé avec shell=True, selon la question? Ce qui n'est pas dans votre code.
Anon
anon, je vous remercie d'avoir souligné l'utilisation déroutante du mot «terminé». Le gestionnaire de contexte attend la fin du sous-processus avant de passer à l'instruction d'impression sur la dernière ligne. C'est différent de terminer instantanément le processus en utilisant quelque chose comme un sigterm. Vous pourriez avoir le même résultat en utilisant un thread et en appelant thread.join(). J'espère que cela clarifie les choses.
Jaiyam Sharma