Как завершить подпроцесс python, запущенный с shell = True

308

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

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

Тем не менее, когда я пытаюсь убить с помощью:

p.terminate()

или

p.kill()

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

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

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

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

user175259
источник
Как ты cmdвыглядишь? Он может содержать команду, запускающую несколько процессов. Так что не ясно, о каком процессе вы говорите.
Роберт Симер
1
не имеет shell=Trueбольшого значения?
Чарли Паркер

Ответы:

410

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

Вот код:

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
источник
2
@PiotrDobrogost: К сожалению, нет, потому что os.setsidне доступен в Windows ( docs.python.org/library/os.html#os.setsid ), я не знаю, может ли это помочь, но вы можете посмотреть здесь ( bugs.python.org / Issue 5115 ) для некоторых о том, как это сделать.
mouad
6
Какое subprocess.CREATE_NEW_PROCESS_GROUPотношение это имеет к этому?
Петр Доброгост
11
наше тестирование предполагает, что setsid! = setpgid и os.pgkill убивают только подпроцессы, которые все еще имеют тот же идентификатор группы процессов. процессы, которые изменили группу процессов, не уничтожаются, даже если они все еще имеют одинаковый идентификатор сеанса ...
hwjp
4
Я бы не рекомендовал делать os.setsid (), так как он имеет и другие эффекты. Среди прочего, он отключает управляющий TTY и делает новый процесс лидером группы процессов. См. Win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
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() должно сработать.

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

Брайант Хансен
источник
1
Хорошее и легкое решение для * nix, спасибо! Работает на Linux, должен работать и на Mac.
MarSoft
Очень хорошее решение. Если ваш cmd оказывается оболочкой сценария оболочки для чего-то еще, вызовите последний двоичный файл там тоже с помощью exec, чтобы иметь только один подпроцесс.
Николинукс
1
Это прекрасно. Я пытался выяснить, как порождать и убивать подпроцесс для каждого рабочего пространства в Ubuntu. Этот ответ помог мне. Жаль, что я мог бы не раз это проголосовать
Сергей Колодяжный
2
это не работает, если в cmd используется
точка с запятой
@speedyrazor - не работает на Windows10. Я думаю, что конкретные ответы должны быть четко обозначены как таковые.
DarkLight
48

Если вы можете использовать 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)
Jovik
источник
3
AttributeError: 'Process' object has no attribute 'get_childrenдля pip install psutil.
d33tah
3
Я думаю, что get_children () должен быть children (). Но это не работает для меня в Windows, процесс все еще там.
Godsmith
3
@Godsmith - API psutil изменился, и вы правы: children () делает то же самое, что и get_children (). Если это не работает в Windows, то вы можете создать тикет об ошибке в GitHub
Jovik
24

Я мог бы сделать это с помощью

from subprocess import Popen

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

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

(В Windows)

SPratap
источник
Или используйте имя процесса : Popen("TASKKILL /F /IM " + process_name)если у вас его нет, вы можете получить его из commandпараметра.
Цви
12

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

Сай Венкат
источник
8

Ни один из этих ответов не работал для меня, поэтому я оставляю код, который работал. В моем случае даже после завершения процесса .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 ... и завершает процесс.

Epinal
источник
Это решение не работает для меня в Linux и Python 2.7
user9869932
@xyz Это сработало для меня в Linux и Python 3.5. Проверьте документы для Python 2.7
epinal
@ espinal, спасибо, да. Возможно, это проблема Linux. Это Raspbian Linux работает на Raspberry 3
user9869932
5

Как сказал Сай, оболочка является дочерней, поэтому она перехватывает сигналы - лучший способ, который я нашел, - это использовать 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
В моем случае это не очень помогает, учитывая, что cmd это "путь к CD && zsync и т. Д.". Так что на самом деле команда терпит неудачу!
user175259
4
Используйте абсолютные пути вместо смены каталогов ... При желании os.chdir (...) в этот каталог ...
Мэтт Билленштайн,
Возможность изменить рабочий каталог для дочернего процесса является встроенной. Просто передайте cwdаргумент Popen.
Джонатон Рейнхарт
Я использовал shlex, но проблема остается, kill не убивает дочерние процессы.
голодный волк
1

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

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

Я знаю, что это старый вопрос, но это может помочь кому-то искать другой метод. Это то, что я использую в 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.

Zx4161
источник
0

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

    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
источник
0

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

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

Я считаю, что этот метод также кроссплатформенный.

Джайям Шарма
источник
Я не вижу, как код здесь завершает процесс. Вы можете уточнить?
DarkLight
Я четко заявил, что если все, что вы хотите сделать, это убедиться, что ваш процесс завершен, прежде чем перейти к следующей строке вашего кода, вы можете использовать этот метод. Я не утверждал, что это прекращает процесс.
Джайям Шарма
Если контекст менеджер «гарантирует , что ваш процесс завершается», как вы заявили четко, то вы делаете заявление , что она завершает процесс. Это правда? Даже когда процесс запускается с shell=Trueпомощью вопроса? Которого нет в вашем коде.
Anon
Спасибо, что указали на запутанное использование слова «прекращено». Менеджер контекста ожидает завершения подпроцесса, прежде чем перейти к оператору печати в последней строке. Это отличается от мгновенного завершения процесса с использованием чего-то вроде сигтермы. Вы можете получить тот же результат, используя поток и вызов thread.join(). Надеюсь, это прояснит это.
Джайям Шарма