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


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

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

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

p.terminate()

или

p.kill()

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

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

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

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

7 234

7 ответов:

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

вот код:

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
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() должны работать.

Я не знаю, какой эффект это будет иметь на вашей трубе, хотя.

Если вы можете использовать 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)

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

from subprocess import Popen

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

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

(В Windows)

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

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

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