Как завершить подпроцесс python, запущенный с shell = True - PullRequest
267 голосов
/ 25 января 2011

Я запускаю подпроцесс следующей командой:

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

Однако, когда я пытаюсь убить, используя:

p.terminate()

или

p.kill()

Команда продолжает работать в фоновом режиме, поэтому мне было интересно, как я могу на самом деле завершить процесс.

Обратите внимание, что когда я запускаю команду с:

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

Он успешно завершается при выдаче p.terminate().

Ответы [ 10 ]

367 голосов
/ 25 января 2011

Используйте группу процессов , чтобы разрешить отправку сигнала всем процессам в группах.Для этого вы должны прикрепить идентификатор сессии к родительскому процессу порожденных / дочерних процессов, который является оболочкой в ​​вашем случае.Это сделает его лидером группы процессов.Теперь, когда сигнал отправляется руководителю группы процессов, он передается всем дочерним процессам этой группы.

Вот код:

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
83 голосов
/ 30 октября 2012
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill() заканчивается завершением процесса оболочки, а cmd все еще выполняется.

Я нашел удобное решение:

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

Это заставит cmd наследовать процесс оболочки вместо того, чтобы оболочка запускала дочерний процесс, который не был уничтожен. p.pid будет идентификатором вашего процесса cmd.

p.kill() должно работать.

Я не знаю, какое влияние это окажет на вашу трубу.

40 голосов
/ 05 августа 2014

Если вы можете использовать psutil , то это прекрасно работает:

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)
21 голосов
/ 12 июля 2013

Я мог бы сделать это, используя

from subprocess import Popen

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

, это убило cmd.exe и программу, для которой я дал команду.

(В Windows)

8 голосов
/ 25 января 2011

Когда shell=True, оболочка является дочерним процессом, а команды - его дочерними.Таким образом, любой SIGTERM или SIGKILL убьет оболочку, но не ее дочерние процессы, и я не помню хорошего способа сделать это.Лучший способ, который я могу придумать, - это использовать shell=False, иначе, когда вы убьете родительский процесс оболочки, он оставит несуществующий процесс оболочки.

6 голосов
/ 01 марта 2018

Ни один из этих ответов не работал для меня, поэтому я оставляю код, который работал. В моем случае даже после завершения процесса с помощью .kill() и получения кода возврата .poll() процесс не завершился.

В соответствии с subprocess.Popen документацией :

"... для правильной очистки приложение с хорошим поведением должно завершить дочерний процесс и завершить связь ..."

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

В моем случае я пропустил proc.communicate() после вызова proc.kill(). Это очищает процесс stdin, stdout ... и завершает процесс.

5 голосов
/ 25 января 2011

Как сказал Сай, оболочка является дочерней, поэтому она перехватывает сигналы - лучший способ, который я нашел, это использовать shell = False и использовать shlex для разделения командной строки:

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

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

Тогда p.kill () и p.terminate () должны работать так, как вы ожидаете.

1 голос
/ 29 ноября 2018

что я чувствую, что мы могли бы использовать:

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

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

это не убьет всю вашу задачу, но процесс с p.pid

0 голосов
/ 05 ноября 2018

Послать сигнал всем процессам в группе

    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 голосов
/ 22 октября 2018

Я знаю, что это старый вопрос, но это может помочь кому-то искать другой метод. Это то, что я использую в Windows, чтобы убить процессы, которые я вызвал.

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

/ IM - имя изображения, вы также можете сделать / PID, если хотите. / T убивает процесс, а также дочерние процессы. / F сила прекращает это. Си, как я установил, это то, как вы делаете это, не показывая окно CMD. Этот код используется в Python 3.

...