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

17 ответов

Решение

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

Вот код:

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

Есть очень простой способ Python 3.5 или + (Фактически протестировано на Python 3.8)

import subprocess, signal, time
p = subprocess.Popen(['cmd'], shell=True)
time.sleep(5) #Wait 5 secs before killing
p.send_signal(signal.CTRL_C_EVENT)

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

try:
    FailingCode #here goes the code which is raising KeyboardInterrupt
except KeyboardInterrupt:
    pass

Этот код просто отправляет сигнал "CTRL+C" запущенному процессу, что приводит к его завершению.

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

    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)

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

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

Решение, которое сработало для меня

      if os.name == 'nt':  # windows
    subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))
else:
    os.kill(process.pid, signal.SIGTERM)

Полнофункциональное решение, которое убьет запущенный процесс (включая поддерево) по истечении времени ожидания или при определенных условиях с помощью функции обратного вызова. Работает как в Windows, так и в Linux, от Python 2.7 до 3.10 на момент написания этой статьи.

Установить с помощью pip install command_runner

Пример тайм-аута:

      from command_runner import command_runner

# Kills ping after 2 seconds
exit_code, output = command_runner('ping 127.0.0.1', shell=True, timeout=2)

Пример для конкретного условия: здесь мы остановим пинг, если текущее число секунд системного времени > 5

      from time import time
from command_runner import command_runner

def my_condition():
    # Arbitrary condition for demo
    return True if int(str(int(time()))[-1]) > 5

# Calls my_condition() every second (check_interval) and kills ping if my_condition() returns True
exit_code, output = command_runner('ping 127.0.0.1', shell=True, stop_on=my_condition, check_interval=1)

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

Команда pkill убивает как родительский, так и весь дочерний процесс

      os.system("pkill -TERM -P %s"%process.pid)

У меня это работает на Pyhton 3.8.10 Win 10.

Я пробовал subprocess.Popen shell true:

  1. subprocess.Popen.kill
  2. subprocess.Popen.terminate

но это /questions/26503954/kak-zavershit-podprotsess-python-zapuschennyij-s-shelltrue/26503969#26503969

Не используйте os.kill().

      import subprocess

class ExecutableProgram:
    program = None

    def __init__(self,path):
        self.path = path
        
    
    def startProgram(self):
        self.program = subprocess.Popen(self.path)


    def stopProgram(self):
        self.program.kill()

И вот как вы можете его использовать:

      from ExecutableProgram import ExecutableProgram


program = ExecutableProgram(r"<your executable>")

program.startProgram()
input()
program.stopProgram()

Хотя это старый вопрос, у него высокий рейтинг в Google. поэтому я решил опубликовать ответ с помощью нового метода, который кто-то может использовать в Python 3, чтобы справиться с этим легко и уверенно. начиная с python 3.5 вы добавили новый метод в subprocess пакет называется run().

Как говорится в документации:

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

Subprocess.run():

Выполняет команду, описанную аргументом args. Дождитесь завершения команды, затем верните CompletedProcess пример.

например, можно запустить этот фрагмент в консоли Python:

>>> subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

PS В случае конкретного вопроса ОП, я не смог воспроизвести его проблему. команды, которые я выполняю с помощью popen(), завершаются правильно.

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

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

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

Другие вопросы по тегам