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