Як закінчити підпроцес 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

Використовуйте групу процесів , щоб увімкнути надсилання сигналу всьому процесу в групах. Для цього слід приєднати ідентифікатор сеансу до батьківського процесу spawned / дочірніх процесів, що є оболонкою у вашому випадку. Це зробить її груповим лідером процесів. Отже, коли сигнал направляється керівнику групи процесів, він передається всім дочірнім процесам цієї групи.

Ось код:

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 / issue5115 ), щоб дізнатися, як це зробити.
— муад

6
Як subprocess.CREATE_NEW_PROCESS_GROUPставиться до цього?
— Пьотр Доброгост

11
наші тестові підпрограми, що встановлюють! (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, щоб мати лише один підпроцес.
— Nicolinux

1
Це прекрасно. Я намагався розібратися, як створити нересурс і вбити підпроцес на робочу область в Ubuntu. Ця відповідь мені допомогла. Бажаю, щоб я міг підкреслити це не раз
— Сергій Колодяжний,

2
це не спрацьовує, якщо
— напівкрапка

@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 () повинні бути дітьми (). Але це не спрацювало для мене в Windows, процес все ще є.
— Godsmith

3
@Godsmith - API psutil змінився, і ти маєш рацію: діти () роблять те саме, що раніше використовували 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параметра.
— zvi

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 path && zsync etc. etc.". Таким чином, це фактично робить команду відмовити!
— користувач175259

4
Використовуйте абсолютні шляхи замість того, щоб змінювати каталоги ... За бажанням os.chdir (...) до цього каталогу ...
— Метт Білленштейн,

Вбудована можливість зміни робочого каталогу для дочірнього процесу. Просто передайте cwdаргумент Popen.
— Джонатан Райнхарт

Я використовував shlex, але все-таки проблема зберігається, вбивство не вбиває процесів дитини.
— голоднийВовк

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, якщо хочете. / Т вбиває процес так само, як і дочірні процеси. / F сила припиняє його. si, як я встановив, це, як ви це робите, не показуючи вікно 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

Я чітко заявив, що якщо все, що ви хочете зробити, переконайтеся, що ваш процес закінчився, перш ніж перейти до наступного рядка вашого коду, ви можете використовувати цей метод. Я не стверджував, що це припиняє процес.
— Jaiyam Sharma

Якщо контекст менеджер «гарантує , що ваш процес завершується», як ви заявили чітко, то ви робите заяву , що вона завершує процес. Це все-таки? Навіть коли процес запускається shell=True, відповідно до питання? Якого немає у вашому коді.
— неділя

на жаль, дякую, що вказали на заплутане вживання слова "припинено". Менеджер контексту очікує завершення підпроцесу перед тим, як перейти до оператора друку в останньому рядку. Це відрізняється від миттєвого припинення процесу, використовуючи щось на зразок ознак. Ви можете мати такий же результат, використовуючи потоки та дзвінки thread.join(). Сподіваюся, це очистить це.
— Jaiyam Sharma
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.