So beenden Sie einen Python-Unterprozess, der mit shell = True gestartet wurde

308

Ich starte einen Unterprozess mit dem folgenden Befehl:

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

Wenn ich jedoch versuche zu töten mit:

p.terminate()

oder

p.kill()

Der Befehl wird weiterhin im Hintergrund ausgeführt, daher habe ich mich gefragt, wie ich den Prozess tatsächlich beenden kann.

Beachten Sie Folgendes, wenn ich den Befehl ausführe mit:

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

Es wird erfolgreich beendet, wenn das ausgegeben wird p.terminate().

user175259
quelle
Wie cmdsiehst du aus? Es kann einen Befehl enthalten, der das Starten mehrerer Prozesse auslöst. Es ist also nicht klar, über welchen Prozess Sie sprechen.
Robert Siemer
1
macht es keinen shell=Truegroßen Unterschied?
Charlie Parker

Antworten:

410

Verwenden Sie eine Prozessgruppe , um das Senden eines Signals an alle Prozesse in den Gruppen zu ermöglichen. Dazu sollten Sie dem übergeordneten Prozess der erzeugten / untergeordneten Prozesse eine Sitzungs-ID hinzufügen, die in Ihrem Fall eine Shell ist. Dies macht es zum Gruppenleiter der Prozesse. Wenn nun ein Signal an den Prozessgruppenleiter gesendet wird, wird es an alle untergeordneten Prozesse dieser Gruppe übertragen.

Hier ist der 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
quelle
2
@PiotrDobrogost: Leider nein, da os.setsides in Windows nicht verfügbar ist ( docs.python.org/library/os.html#os.setsid ), weiß ich nicht, ob dies helfen kann, aber Sie können hier nachsehen ( bugs.python.org) / issue5115 ) für einen Einblick, wie es geht.
Mouad
6
Wie hängt subprocess.CREATE_NEW_PROCESS_GROUPdas damit zusammen?
Piotr Dobrogost
11
Unsere Tests schlagen vor, dass setsid! = setpgid und os.pgkill nur Unterprozesse abbricht, die immer noch dieselbe Prozessgruppen-ID haben. Prozesse, die die Prozessgruppe geändert haben, werden nicht beendet, obwohl sie möglicherweise immer noch dieselbe Sitzungs-ID haben ...
hwjp
4
Ich würde os.setsid () nicht empfehlen, da es auch andere Effekte hat. Unter anderem wird das steuernde TTY getrennt und der neue Prozess zum Prozessgruppenleiter gemacht. Siehe win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()beendet den Shell-Prozess und cmdläuft noch.

Ich fand eine bequeme Lösung, indem ich:

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

Dies führt dazu, dass cmd den Shell-Prozess erbt, anstatt dass die Shell einen untergeordneten Prozess startet, der nicht beendet wird. p.pidwird dann die ID Ihres cmd-Prozesses sein.

p.kill() sollte arbeiten.

Ich weiß allerdings nicht, welche Auswirkungen dies auf Ihre Pfeife haben wird.

Bryant Hansen
quelle
1
Schöne und leichte Lösung für * nix, danke! Funktioniert unter Linux, sollte auch für Mac funktionieren.
MarSoft
Sehr schöne Lösung. Wenn Ihr cmd zufällig ein Shell-Skript-Wrapper für etwas anderes ist, rufen Sie dort auch die letzte Binärdatei mit exec auf, um nur einen Unterprozess zu haben.
Nicolinux
1
Das ist schön. Ich habe versucht herauszufinden, wie ein Unterprozess pro Arbeitsbereich unter Ubuntu erzeugt und beendet werden kann. Diese Antwort hat mir geholfen. Ich wünschte, ich könnte es mehr als einmal
positiv bewerten
2
Dies funktioniert nicht, wenn ein
Semikolon
@speedyrazor - Funktioniert nicht unter Windows10. Ich denke, unsere spezifischen Antworten sollten eindeutig als solche gekennzeichnet sein.
DarkLight
48

Wenn Sie Psutil verwenden können , funktioniert dies perfekt:

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
quelle
3
AttributeError: 'Process' object has no attribute 'get_childrenfür pip install psutil.
d33tah
3
Ich denke, get_children () sollte Kinder () sein. Aber es hat bei mir unter Windows nicht funktioniert, der Prozess ist noch da.
Godsmith
3
@Godsmith - Die psutil-API hat sich geändert und Sie haben Recht: children () macht dasselbe wie früher get_children (). Wenn es unter Windows nicht funktioniert, möchten Sie möglicherweise ein Bug-Ticket in GitHub
Jovik
24

Ich könnte es mit tun

from subprocess import Popen

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

es tötete das cmd.exeund das Programm, für das ich den Befehl gab.

(Unter Windows)

SPratap
quelle
Oder verwenden Sie den Prozessnamen : Popen("TASKKILL /F /IM " + process_name)Wenn Sie ihn nicht haben, können Sie ihn über den commandParameter abrufen.
zvi
12

Wenn shell=Truedie Shell der untergeordnete Prozess ist und die Befehle ihre untergeordneten Prozesse sind. Also jeder SIGTERModer SIGKILLwird die Shell töten, aber nicht ihre untergeordneten Prozesse, und ich erinnere mich nicht an einen guten Weg, dies zu tun. Der beste Weg, den ich mir vorstellen kann, ist die Verwendung shell=False. Andernfalls wird beim Beenden des übergeordneten Shell-Prozesses ein nicht mehr funktionierender Shell-Prozess zurückgelassen.

Sai Venkat
quelle
8

Keine dieser Antworten hat bei mir funktioniert, also lasse ich den Code, der funktioniert hat. In meinem Fall wurde der Prozess auch nach dem Beenden des Prozesses mit .kill()und dem Abrufen eines .poll()Rückkehrcodes nicht beendet.

Befolgen Sie die subprocess.Popen Dokumentation :

"... um eine ordnungsgemäße Bereinigung zu erreichen, sollte eine gut erzogene Anwendung den untergeordneten Prozess beenden und die Kommunikation beenden ..."

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

In meinem Fall fehlte mir das proc.communicate()nach dem Anruf proc.kill(). Dies bereinigt den Prozess stdin, stdout ... und beendet den Prozess.

epinal
quelle
Diese Lösung funktioniert nicht für mich unter Linux und Python 2.7
user9869932
@xyz Es hat bei mir unter Linux und Python 3.5 funktioniert. Überprüfen Sie die Dokumente für Python 2.7
Epinal
@espinal, danke, ja. Es ist möglicherweise ein Linux-Problem. Es ist Raspbian Linux läuft auf einem Raspberry 3
user9869932
5

Wie Sai sagte, ist die Shell das Kind, also werden Signale von ihr abgefangen - der beste Weg, den ich gefunden habe, ist Shell = False zu verwenden und Shlex zu verwenden, um die Befehlszeile zu teilen:

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

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

Dann sollten p.kill () und p.terminate () so funktionieren, wie Sie es erwarten.

Matt Billenstein
quelle
1
In meinem Fall hilft es nicht wirklich, wenn man bedenkt, dass cmd "cd path && zsync etc etc" ist. Das lässt den Befehl also tatsächlich fehlschlagen!
user175259
4
Verwenden Sie absolute Pfade, anstatt Verzeichnisse zu ändern ... Optional os.chdir (...) zu diesem Verzeichnis ...
Matt Billenstein
Die Möglichkeit, das Arbeitsverzeichnis für den untergeordneten Prozess zu ändern, ist integriert. Geben Sie das cwdArgument einfach an weiter Popen.
Jonathon Reinhart
Ich habe Shlex verwendet, aber das Problem besteht weiterhin. Töten bedeutet nicht, die untergeordneten Prozesse zu beenden.
hungriger Wolf
1

Wie ich denke, könnten wir gebrauchen:

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

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

Dies wird nicht alle Ihre Aufgaben beenden, sondern den Prozess mit der p.pid

Nilesh K.
quelle
0

Ich weiß, dass dies eine alte Frage ist, aber dies kann jemandem helfen, der nach einer anderen Methode sucht. Dies ist, was ich unter Windows verwende, um Prozesse abzubrechen, die ich aufgerufen habe.

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

/ IM ist der Bildname, Sie können auch / PID ausführen, wenn Sie möchten. / T beendet den Prozess sowie die untergeordneten Prozesse. / F Kraft beendet es. si, wie ich es eingestellt habe, ist, wie Sie dies tun, ohne ein CMD-Fenster anzuzeigen. Dieser Code wird in Python 3 verwendet.

Zx4161
quelle
0

Senden Sie das Signal an alle Prozesse in der Gruppe

    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
quelle
0

Ich habe dies hier nicht erwähnt gesehen, also stelle ich es dort heraus, falls jemand es braucht. Wenn Sie lediglich sicherstellen möchten, dass Ihr Unterprozess erfolgreich beendet wird, können Sie ihn in einen Kontextmanager einfügen. Ich wollte beispielsweise, dass mein Standarddrucker ein ausgedrucktes Bild druckt und mithilfe des Kontextmanagers sicherstellt, dass der Unterprozess beendet wird.

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

Ich glaube, diese Methode ist auch plattformübergreifend.

Jaiyam Sharma
quelle
Ich kann nicht sehen, wie der Code hier einen Prozess beendet. Könntest Du das erläutern?
DarkLight
Ich habe klar gesagt, dass Sie diese Methode verwenden können, wenn Sie nur sicherstellen möchten, dass Ihr Prozess beendet wurde, bevor Sie mit der nächsten Zeile Ihres Codes fortfahren. Ich habe nicht behauptet, dass es den Prozess beendet.
Jaiyam Sharma
Wenn der Kontext - Manager „stellt sicher , dass Ihr Prozess beendet ist “, wie Sie klar gesagt haben, dann sind Sie tun behaupten , dass sie den Prozess beendet. Tut es aber? Auch wenn der Prozess mit gestartet wird shell=True, gemäß der Frage? Was es nicht in Ihrem Code ist.
Anon
anon, danke, dass du auf die verwirrende Verwendung des Wortes "beendet" hingewiesen hast. Der Kontextmanager wartet, bis der Unterprozess abgeschlossen ist, bevor er mit der Druckanweisung in der letzten Zeile fortfährt. Dies unterscheidet sich vom sofortigen Beenden des Prozesses mit einem Sigterm. Sie könnten das gleiche Ergebnis erzielen, wenn Sie einen Thread verwenden und aufrufen thread.join(). Hoffe das klärt es auf.
Jaiyam Sharma