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

Posted

技术标签:

【中文标题】如何终止使用 shell=True 启动的 python 子进程【英文标题】:How to terminate a python subprocess launched with shell=True 【发布时间】:2011-06-14 23:02:57 【问题描述】:

我正在使用以下命令启动一个子进程:

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

但是,当我尝试使用以下方法杀死时:

p.terminate()

p.kill()

该命令一直在后台运行,所以我想知道如何才能真正终止该进程。

请注意,当我使用以下命令运行命令时:

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

发出p.terminate()时它确实成功终止。

【问题讨论】:

您的cmd 是什么样的?它可能包含触发多个进程启动的命令。所以不清楚你说的是哪个过程。 相关:Python: how to kill child process(es) when parent dies? 没有shell=True 会有很大的不同吗? 【参考方案1】:

pkill 命令杀死父进程和所有子进程

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

【讨论】:

【参考方案2】:

这适用于 Pyhton 3.8.10 Win 10。

我试过 subprocess.Popen shell tr​​ue

    subprocess.Popen.kill subprocess.Popen.terminate

但这确实https://***.com/a/4789957/8518485

不要使用 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):
        print(ExecutableProgram.stopProgram.__name__)
        sleep(.5)

        processName = self.program.name()

        for proc in psutil.process_iter():
            if processName in proc.name():
                parent_pid = proc.pid

        parent = psutil.Process(parent_pid)
        for child in parent.children(recursive=False): 
            child.kill()
        parent.kill(

这是你如何使用它:

from ExecutableProgram import ExecutableProgram


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

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

【讨论】:

【参考方案3】:

这些答案都不适合我,所以我留下了有效的代码。在我的情况下,即使在使用.kill() 终止进程并获得.poll() 返回码后,进程也没有终止。

关注subprocess.Popendocumentation:

"...为了正确清理,一个表现良好的应用程序应该终止子进程并完成通信..."

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

在我的情况下,我在调用proc.kill() 后错过了proc.communicate()。这会清除进程 stdin、stdout ... 并终止进程。

【讨论】:

这个解决方案在 linux 和 python 2.7 中对我不起作用 @xyz 它在 Linux 和 python 3.5 中对我有用。检查 python 2.7 的文档 @espinal,谢谢,是的。可能是linux的问题。它是在 Raspberry 3 上运行的 Raspbian linux【参考方案4】:

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”信号,这会导致进程被杀死。

【讨论】:

注意:这是特定于 Wndows 的。在signal 的 Mac 或 Linux 实现中没有定义 CTRL_C_EVENT。可以找到一些替代代码(我没有测试过)here。【参考方案5】:

向组中的所有进程发送信号

    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)

【讨论】:

【参考方案6】:

如果你可以使用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)

【讨论】:

AttributeError: 'Process' object has no attribute 'get_childrenpip install psutil 我认为 get_children() 应该是 children()。但它在 Windows 上对我不起作用,该过程仍然存在。 @Godsmith - psutil API 已更改,您是对的:children() 与 get_children() 过去所做的相同。如果它在 Windows 上不起作用,那么您可能需要在 GitHub 中创建错误票证 如果子 X 在为子 A 调用 proc.kill() 期间创建子 SubX,这将不起作用 由于某种原因,经过多次尝试,其他解决方案对我不起作用,但这个解决方案可以!【参考方案7】:

shell=True 时,shell 是子进程,命令是它的子进程。所以任何SIGTERMSIGKILL 都会杀死shell,但不会杀死它的子进程,我不记得有什么好方法了。 我能想到的最好的方法是使用shell=False,否则当你杀死父shell进程时,它会留下一个已失效的shell进程。

【讨论】:

【参考方案8】:

使用process group 以便向组中的所有进程发送信号。为此,您应该将 session 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

【讨论】:

subprocess.CREATE_NEW_PROCESS_GROUP 与此有何关系? 我们的测试表明 setsid != setpgid,并且 os.pgkill 只杀死仍然具有相同进程组 ID 的子进程。已更改进程组的进程不会被杀死,即使它们可能仍具有相同的会话 id... @PiotrDobrogost: CREATE_NEW_PROCESS_GROUP can be used to emulate start_new_session=True on Windows 我不建议使用 os.setsid(),因为它还有其他效果。其中,它断开控制 TTY 并使新进程成为进程组领导。见win.tue.nl/~aeb/linux/lk/lk-10.html 您将如何在 Windows 中执行此操作? setsid 仅在 *nix 系统上可用。【参考方案9】:

我可以用

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参数中获取。【参考方案10】:
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() 应该可以工作。

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

【讨论】:

*nix 的不错的轻量级解决方案,谢谢!适用于 Linux,也应适用于 Mac。 非常好的解决方案。如果您的 cmd 恰好是其他东西的 shell 脚本包装器,请也使用 exec 调用最终的二进制文件,以便只有一个子进程。 这很漂亮。我一直在试图弄清楚如何在 Ubuntu 上为每个工作区生成和杀死一个子进程。这个答案帮助了我。希望我能不止一次地支持它 如果在cmd中使用分号则不起作用 @speedyrazor - 不适用于 Windows10。我认为操作系统的具体答案应该清楚地标记出来。【参考方案11】:

正如 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() 应该会按照您的预期工作。

【讨论】:

在我的情况下,考虑到 cmd 是“cd path && zsync etc etc”,它并没有真正的帮助。所以这实际上使命令失败! 使用绝对路径而不是更改目录...可选 os.chdir(...) 到该目录... 内置更改子进程工作目录的功能。只需将cwd 参数传递给Popen 我使用了shlex,但问题依旧,kill 不是杀死子进程。

以上是关于如何终止使用 shell=True 启动的 python 子进程的主要内容,如果未能解决你的问题,请参考以下文章

终止正在运行的子进程调用

如何在Linux中使用Shell脚本终止用户会话?

delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件

PySide Qt 脚本不能从 Spyder 启动,但可以从 shell 运行

孤儿(ksh Shell 脚本未在 CTRL-X 上首先终止)

如何在 Python 中终止我的子进程 [重复]