Come terminare un sottoprocesso di Python avviato con shell = True


308

Sto avviando un sottoprocesso con il seguente comando:

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

Tuttavia, quando provo a uccidere usando:

p.terminate()

o

p.kill()

Il comando continua a essere eseguito in background, quindi mi chiedevo come posso effettivamente terminare il processo.

Si noti che quando eseguo il comando con:

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

Si interrompe correttamente durante l'emissione del file p.terminate().


Com'è il tuo cmdaspetto? Potrebbe contenere un comando che avvia diversi processi da avviare. Quindi non è chiaro di quale processo parli.
Robert Siemer,


1
non shell=Truefare la differenza?
Charlie Parker

Risposte:


410

Utilizzare un gruppo di processi in modo da consentire l'invio di un segnale a tutto il processo nei gruppi. Per questo, è necessario collegare un ID di sessione al processo padre dei processi generati / figlio, che è una shell nel tuo caso. Questo lo renderà il capogruppo dei processi. Quindi ora, quando un segnale viene inviato al leader del gruppo di processi, viene trasmesso a tutti i processi figlio di questo gruppo.

Ecco il codice:

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

2
@PiotrDobrogost: Purtroppo no, perché os.setsidnon è disponibile in Windows ( docs.python.org/library/os.html#os.setsid ), non so se questo può aiutare ma puoi guardare qui ( bugs.python.org / issue5115 ) per alcune informazioni su come farlo.
mouad,

6
Come si subprocess.CREATE_NEW_PROCESS_GROUPcollega a questo?
Piotr Dobrogost,

11
i nostri suggerimenti di test che setsid! = setpgid e che os.pgkill uccidono solo sottoprocessi che hanno ancora lo stesso ID del gruppo di processi. i processi che hanno cambiato il gruppo di processi non vengono uccisi, anche se possono avere ancora lo stesso ID sessione ...
hwjp


4
Non consiglierei di fare os.setsid (), poiché ha anche altri effetti. Tra l'altro, disconnette il TTY di controllo e rende il nuovo processo un leader del gruppo di processo. Vedi win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje

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

p.kill()finisce per uccidere il processo di shell ed cmdè ancora in esecuzione.

Ho trovato una soluzione conveniente da:

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

Questo farà sì che cmd erediti il ​​processo della shell, invece di far avviare alla shell un processo figlio, che non viene ucciso. p.pidsarà quindi l'id del tuo processo cmd.

p.kill() dovrebbe funzionare.

Non so quale effetto avrà questo sulla tua pipa.


1
Soluzione piacevole e leggera per * nix, grazie! Funziona su Linux, dovrebbe funzionare anche per Mac.
MarSoft,

Soluzione molto bella. Se il tuo cmd sembra essere un wrapper di script shell per qualcos'altro, chiama anche il binario finale lì con exec per avere un solo sottoprocesso.
Nicolinux

1
Questo è bellissimo. Ho cercato di capire come generare e uccidere un sottoprocesso per area di lavoro su Ubuntu. Questa risposta mi ha aiutato. Vorrei poterlo votare più di una volta
Sergiy Kolodyazhnyy il

2
questo non funziona se si usa un punto e virgola nel cmd
gnr del

@speedyrazor - Non funziona su Windows10. Penso che le risposte specifiche del sistema operativo debbano essere chiaramente indicate come tali.
DarkLight,

48

Se puoi usare psutil , allora funziona perfettamente:

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)

3
AttributeError: 'Process' object has no attribute 'get_childrenper pip install psutil.
d33tah,

3
Penso che get_children () dovrebbe essere children (). Ma non ha funzionato per me su Windows, il processo è ancora lì.
Godsmith,

3
@Godsmith - l'API psutil è cambiata e hai ragione: children () fa la stessa cosa di get_children (). Se non funziona su Windows, potresti creare un ticket bug in GitHub
Jovik

24

Potrei farlo usando

from subprocess import Popen

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

ha ucciso il cmd.exee il programma per il quale ho dato il comando.

(Su Windows)


Oppure usa il nome del processo : Popen("TASKKILL /F /IM " + process_name)se non lo hai, puoi ottenerlo dal commandparametro.
zvi

12

Quando shell=Truela shell è il processo figlio e i comandi sono i suoi figli. Quindi, qualsiasi SIGTERMo SIGKILLucciderà il guscio, ma non i suoi processi figli, e non mi ricordo un buon modo per farlo. Il modo migliore a cui riesco a pensare è quello di usarlo shell=False, altrimenti quando si interrompe il processo della shell genitore, si lascerà un processo della shell defunto.


8

Nessuna di queste risposte ha funzionato per me, quindi sto lasciando il codice che ha funzionato. Nel mio caso, anche dopo aver terminato il processo .kill()e aver ottenuto un .poll()codice di ritorno, il processo non si è interrotto.

A seguito della subprocess.Popen documentazione :

"... per pulire correttamente un'applicazione ben educata dovrebbe interrompere il processo figlio e terminare la comunicazione ..."

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

Nel mio caso mi mancava la proc.communicate()chiamata dopo proc.kill(). Questo pulisce il processo stdin, stdout ... e termina il processo.


Questa soluzione non funziona per me in Linux e Python 2.7
user9869932

@xyz Ha funzionato per me in Linux e Python 3.5. Controlla i documenti per Python 2.7
Epinal

@espinal, grazie, sì. È forse un problema di Linux. È Raspbian Linux in esecuzione su un Raspberry 3
user9869932

5

Come ha detto Sai, la shell è il bambino, quindi i segnali sono intercettati da essa - il modo migliore che ho trovato è usare shell = False e usare shlex per dividere la riga di comando:

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

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

Quindi p.kill () e p.terminate () dovrebbero funzionare come previsto.


1
Nel mio caso non è di grande aiuto dato che cmd è "percorso cd && zsync etc etc". In questo modo il comando fallisce!
user175259

4
Usa percorsi assoluti invece di cambiare directory ... Opzionalmente os.chdir (...) in quella directory ...
Matt Billenstein,

La possibilità di modificare la directory di lavoro per il processo figlio è integrata. Passa l' cwdargomento a Popen.
Jonathon Reinhart,

Ho usato shlex, ma il problema persiste, uccidere non sta uccidendo i processi figlio.
affamato Wolf

1

cosa sento che potremmo usare:

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

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

questo non ucciderà tutte le tue attività ma il processo con p.pid


0

So che questa è una vecchia domanda, ma ciò può aiutare qualcuno alla ricerca di un metodo diverso. Questo è quello che uso su Windows per uccidere i processi che ho chiamato.

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

/ IM è il nome dell'immagine, puoi anche fare / PID se lo desideri. / T uccide sia il processo che i processi figlio. / F force lo termina. si, come ho impostato, è come farlo senza mostrare una finestra CMD. Questo codice è utilizzato in Python 3.


0

Invia il segnale a tutti i processi nel gruppo

    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)

0

Non ho visto questo menzionato qui, quindi lo sto mettendo fuori nel caso qualcuno ne avesse bisogno. Se tutto quello che vuoi fare è assicurarti che il tuo sottoprocesso si concluda correttamente, potresti metterlo in un gestore di contesto. Ad esempio, volevo che la mia stampante standard stampasse un'immagine in uscita e l'utilizzo del gestore del contesto garantiva la chiusura del sottoprocesso.

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

Credo che questo metodo sia anche multipiattaforma.


Non riesco a vedere come il codice qui termina un processo. Puoi chiarire?
DarkLight,

Ho dichiarato chiaramente che se tutto ciò che si desidera fare è assicurarsi che il processo sia terminato prima di passare alla riga successiva del codice, è possibile utilizzare questo metodo. Non ho affermato che interrompe il processo.
Jaiyam Sharma,

Se il gestore di contesto "fa in modo che il processo è terminato", come lei ha detto chiaramente, allora si fa reclamo che termina il processo. Lo fa però? Anche quando il processo viene avviato con shell=True, come da domanda? Che non è nel tuo codice.
anon

anon, grazie per aver sottolineato l'uso confuso della parola "terminato". Il gestore di contesto attende il completamento del processo secondario prima di passare all'istruzione print sull'ultima riga. Questo è diverso dal terminare istantaneamente il processo usando qualcosa come un sigterm. Potresti ottenere lo stesso risultato usando un thread e chiamando thread.join(). Spero che questo chiarisca.
Jaiyam Sharma,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.