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

440 投票
11 回答
510014 浏览
提问于 2025-04-16 10:34

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

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

但是,当我尝试用以下方式结束它时:

p.terminate()

或者

p.kill()

这个命令仍然在后台运行,所以我想知道我该如何真正终止这个进程。

需要注意的是,当我用以下命令运行时:

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

在执行 p.terminate() 时,它确实能够成功终止。

11 个回答

103

如果你能使用 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)
137
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() 应该就能正常工作了。

不过我不太清楚这对你的管道会有什么影响。

546

使用一个 进程组,这样可以让你向组内的所有进程发送信号。为了做到这一点,你需要给你启动的子进程的父进程(在你的情况下是一个shell)附加一个 会话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

撰写回答