Jak zakończyć podprocesję Pythona uruchomioną z shell = True

308

Uruchamiam podproces za pomocą następującego polecenia:

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

Jednak gdy próbuję zabić za pomocą:

p.terminate()

lub

p.kill()

Polecenie działa w tle, więc zastanawiałem się, jak mogę faktycznie zakończyć proces.

Zauważ, że kiedy uruchamiam polecenie z:

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

To kończy się pomyślnie podczas wydawania p.terminate().

użytkownik175259
źródło
Jak cmdwyglądasz Może zawierać polecenie, które uruchamia kilka procesów do uruchomienia. Nie jest więc jasne, o którym procesie mówisz.
Robert Siemer
1
nie shell=Truerobi dużej różnicy?
Charlie Parker,

Odpowiedzi:

410

Użyj grupy procesów , aby umożliwić wysyłanie sygnału do całego procesu w grupach. W tym celu powinieneś dołączyć identyfikator sesji do procesu nadrzędnego procesów spawnowanych / potomnych, który w twoim przypadku jest powłoką. Dzięki temu będzie liderem grupy w procesach. Tak więc teraz, gdy sygnał jest wysyłany do lidera grupy procesów, jest on przekazywany do wszystkich procesów potomnych tej grupy.

Oto kod:

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
źródło
2
@PiotrDobrogost: Niestety nie, ponieważ os.setsidnie jest dostępny w systemie Windows ( docs.python.org/library/os.html#os.setsid ), nie wiem, czy to może pomóc, ale możesz tutaj zajrzeć ( bugs.python.org / issue5115 ), aby uzyskać wgląd w to, jak to zrobić.
mouad
6
Jak to się ma subprocess.CREATE_NEW_PROCESS_GROUPdo tego?
Piotr Dobrogost
11
nasze testy sugerują, że setsid! = setpgid i że os.pgkill zabija tylko podprocesy, które wciąż mają ten sam identyfikator grupy procesów. procesy, które zmieniły grupę procesów, nie są zabijane, nawet jeśli wciąż mają ten sam identyfikator sesji ...
hwjp
4
Nie polecałbym wykonywania os.setsid (), ponieważ ma również inne efekty. Między innymi odłącza sterujący TTY i czyni nowy proces liderem grupy procesów. Zobacz win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()w końcu zabija proces powłoki i cmdnadal działa.

Znalazłem wygodny sposób naprawy:

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

Spowoduje to, że cmd odziedziczy proces powłoki, zamiast zmuszać powłokę do uruchomienia procesu potomnego, który nie zostanie zabity. p.pidbędzie wtedy identyfikatorem twojego procesu cmd.

p.kill() powinno działać.

Nie wiem jednak, jak to wpłynie na twoją fajkę.

Bryant Hansen
źródło
1
Ładne i lekkie rozwiązanie dla * nix, dzięki! Działa w systemie Linux, powinien również działać na komputerach Mac.
MarSoft,
Bardzo fajne rozwiązanie. Jeśli twoje cmd okazuje się być opakowaniem skryptu powłoki dla czegoś innego, wywołaj tam również ostateczny plik binarny z exec, aby mieć tylko jeden podproces.
Nicolinux,
1
To jest piękne. Próbowałem wymyślić, jak spawnować i zabijać podproces na obszar roboczy na Ubuntu. Ta odpowiedź pomogła mi. Chciałbym móc głosować więcej niż raz
Sergiy Kolodyazhnyy
2
nie działa to, jeśli w cmd
użyto średnika
@speedyrazor - Nie działa na Windows10. Myślę, że konkretne odpowiedzi powinny być wyraźnie oznaczone jako takie.
DarkLight,
48

Jeśli możesz użyć psutil , to działa idealnie:

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
źródło
3
AttributeError: 'Process' object has no attribute 'get_childrendla pip install psutil.
d33tah
3
Myślę, że get_children () powinno być dziećmi (). Ale to nie działało dla mnie w systemie Windows, proces jest nadal obecny.
Godsmith,
3
@Godsmith - psutil API zmieniło się i masz rację: children () robi to samo, co kiedyś get_children (). Jeśli to nie działa w systemie Windows, możesz utworzyć bilet błędu w GitHub
Jovik
24

Mógłbym to zrobić za pomocą

from subprocess import Popen

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

zabił program cmd.exei program, dla którego wydałem polecenie.

(W systemie Windows)

SPratap
źródło
Lub użyj nazwy procesu : Popen("TASKKILL /F /IM " + process_name)jeśli go nie masz, możesz uzyskać go z commandparametru.
zvi
12

Gdy shell=Truepowłoka jest procesem potomnym, a polecenia są jej potomkami. Więc każdy SIGTERMlub SIGKILLzabije powłokę, ale nie jego procesów potomnych, a ja nie pamiętam, to dobry sposób, aby to zrobić. Najlepszym sposobem, jaki mogę wymyślić, jest użycie shell=False, w przeciwnym razie, gdy zabijesz proces powłoki nadrzędnej, pozostawi on niedziałający proces powłoki.

Sai Venkat
źródło
8

Żadna z tych odpowiedzi nie działała dla mnie, więc zostawiam kod, który działał. W moim przypadku nawet po zabiciu procesu .kill()i otrzymaniu .poll()kodu powrotu proces nie został zakończony.

Zgodnie z subprocess.Popen dokumentacją :

„... w celu prawidłowego wyczyszczenia dobrze zachowana aplikacja powinna zabić proces potomny i zakończyć komunikację ...”

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

W moim przypadku brakowało połączenia proc.communicate()po proc.kill(). To czyści proces stdin, stdout ... i kończy proces.

epinal
źródło
To rozwiązanie nie działa dla mnie w Linuksie i Pythonie 2.7
user9869932,
@xyz To działało dla mnie w Linuksie i Pythonie 3.5. Sprawdź dokumentację Pythona 2.7
Epinal
@spinal, dzięki, tak. Prawdopodobnie jest to problem z linuksem. Jest to Raspbian linux działający na Raspberry 3
użytkownik9869932,
5

Jak powiedział Sai, powłoka jest dzieckiem, więc przechwytuje przez nią sygnały - najlepszym sposobem, jaki znalazłem, jest użycie shell = False i użycie shlex do podzielenia linii poleceń:

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

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

Następnie p.kill () i p.terminate () powinny działać zgodnie z oczekiwaniami.

Matt Billenstein
źródło
1
W moim przypadku tak naprawdę to nie pomaga, biorąc pod uwagę, że cmd to „ścieżka cd &&ssc itd.”. To powoduje, że polecenie zawiedzie!
user175259
4
Używaj ścieżek bezwzględnych zamiast zmieniać katalogi ... Opcjonalnie os.chdir (...) do tego katalogu ...
Matt Billenstein 26.01.11
Możliwość zmiany katalogu roboczego dla procesu potomnego jest wbudowana. Przekaż cwdargument do Popen.
Jonathon Reinhart
Użyłem shlex, ale problem nadal występuje, zabicie nie zabija procesów potomnych.
głodny Wilk
1

Czuję, że moglibyśmy użyć:

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

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

to nie zabije całego twojego zadania, ale proces z p.pid

Nilesh K.
źródło
0

Wiem, że to stare pytanie, ale może pomóc komuś, kto szuka innej metody. Tego używam w systemie Windows do zabijania wywoływanych przeze mnie procesów.

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

/ IM to nazwa obrazu, możesz także zrobić / PID, jeśli chcesz. / T zabija proces, podobnie jak procesy potomne. Siła / F ją kończy. si, jak już ustawiłem, to sposób, w jaki to robisz bez pokazywania okna CMD. Ten kod jest używany w Pythonie 3.

Zx4161
źródło
0

Wyślij sygnał do wszystkich procesów w grupie

    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
źródło
0

Nie widziałem tego tutaj wspomnianego, więc zamieszczam to na wypadek, gdyby ktoś tego potrzebował. Jeśli wszystko, co chcesz zrobić, to upewnić się, że podproces zakończy się powodzeniem, możesz umieścić go w menedżerze kontekstu. Na przykład chciałem, aby moja standardowa drukarka wydrukowała obraz, a użycie menedżera kontekstu zapewniło zakończenie podprocesu.

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

Uważam, że ta metoda jest również wieloplatformowa.

Jaiyam Sharma
źródło
Nie widzę, jak kod tutaj kończy proces. Możesz wyjaśnić?
DarkLight,
Stwierdziłem wyraźnie, że jeśli wszystko, co chcesz to zrobić, upewnia się, że proces został zakończony przed przejściem do następnego wiersza kodu, możesz użyć tej metody. Nie twierdziłem, że to kończy proces.
Jaiyam Sharma
Jeśli menedżer kontekst „zapewnia, że proces został zakończony”, jak już zaznaczono wyraźnie, to zrobić twierdzenie, że kończy proces. Czy to prawda? Nawet po uruchomieniu procesu shell=True, zgodnie z pytaniem? Którego nie ma w twoim kodzie.
anon
anon, dziękuję za zwrócenie uwagi na mylące użycie słowa „zakończony”. Menedżer kontekstu czeka na zakończenie podprocesu przed przejściem do instrukcji print w ostatnim wierszu. Różni się to od natychmiastowego zakończenia procesu za pomocą czegoś takiego jak sigterm. Możesz uzyskać ten sam wynik za pomocą wątku i wywołania thread.join(). Mam nadzieję, że to wyjaśni.
Jaiyam Sharma