Как завершить подпроцесс 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().


Как ты 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

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)

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)


Или используйте имя процесса : 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 ... и завершает процесс.


Это решение не работает для меня в 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.


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)

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(). Надеюсь, это прояснит это.
— Джайям Шарма
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.