如何终止以shell = True启动的python子进程


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太大的不同吗?
查理·帕克

Answers:


410

使用进程组,以便能够向中的所有进程发送信号。为此,您应该将会话ID附加到生成的子进程的父进程中,在您的情况下,这是一个外壳程序。这将使其成为流程的小组负责人。因此,现在,当信号发送到流程组负责人时,它便被传输到该组的所有子流程。

这是代码:

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),以获取有关操作方法的一些见解。
mouad 2012年

6
如何subprocess.CREATE_NEW_PROCESS_GROUP涉及到这个?
Piotr Dobrogost 2012年

11
我们的测试建议setid!= setpgid,而os.pgkill只杀死仍然具有相同进程组ID的子进程。更改了进程组的进程不会被杀死,即使它们可能仍具有相同的会话ID ...
hwjp 2013年


4
我不建议您使用os.setsid(),因为它还有其他作用。其中,它断开了控制TTY的连接,并使新过程成为过程组负责人。参见win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje 2015年

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

p.kill()最终终止了shell进程,cmd并且仍在运行。

我通过以下方法找到了一个方便的解决方法:

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

这将导致cmd继承shell进程,而不是让shell启动不会被杀死的子进程。 p.pid然后将是您的cmd进程的ID。

p.kill() 应该管用。

我不知道这会对您的管道产生什么影响。


1
* nix不错的解决方案,谢谢!在Linux上可以使用,在Mac上也可以使用。
MarSoft

非常好的解决方案。如果您的cmd恰好是其他内容的Shell脚本包装,请也使用exec调用最终二进制文件,以便只有一个子进程。
Nicolinux

1
这很漂亮。我一直在尝试找出如何在Ubuntu上为每个工作区生成并杀死一个子进程。这个答案帮助了我。希望我能
投票

2
如果在cmd中使用了分号,则无法正常工作
gnr

@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_childrenpip install psutil
d33tah 2015年

3
我认为get_children()应该是children()。但是它在Windows上对我不起作用,该过程仍然存在。
Godsmith

3
@Godsmith-psutil API发生了变化,您说对了:children()与过去的get_children()一样。如果它不工作在Windows上,那么你可能想在创建一个bug票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=Trueshell是子进程时,命令就是它的子进程。因此,任何SIGTERMSIGKILL将杀死外壳程序但不会杀死它的子进程的过程,我不记得有什么好方法。我能想到的最好方法是使用shell=False,否则当您杀死父shell进程时,它将留下一个已失效的shell进程。


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问题。这是在Raspberry 3上运行的Raspbian linux
user9869932

5

正如Sai所说,shell是孩子,因此信号被它拦截了-我发现的最佳方法是使用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”,它实际上并没有帮助。这样实际上会使命令失败!
user175259 2011年

4
使用绝对路径而不是更改目录...可以选择os.chdir(...)到该目录...
Matt Billenstein 2011年

内置了更改子进程工作目录的功能。只需将cwd参数传递给即可Popen
乔纳森·莱因哈特

我使用了shlex,但问题仍然存在,kill并不是杀死子进程。
hungryWolf

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强制终止它。如我所设置的,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,还是会出现问题?它不在您的代码中。
匿名

匿名,感谢您指出“终止”一词的混淆用法。上下文管理器在继续最后一行的打印语句之前,等待子进程完成。这与使用sigterm之类的方法立即终止该过程不同。使用线程并调用,可能会得到相同的结果thread.join()。希望这可以解决。
Jaiyam Sharma
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.