如何终止使用 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 true:
-
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.Popen
documentation:
"...为了正确清理,一个表现良好的应用程序应该终止子进程并完成通信..."
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_children
为pip install psutil
。
我认为 get_children() 应该是 children()。但它在 Windows 上对我不起作用,该过程仍然存在。
@Godsmith - psutil API 已更改,您是对的:children() 与 get_children() 过去所做的相同。如果它在 Windows 上不起作用,那么您可能需要在 GitHub 中创建错误票证
如果子 X 在为子 A 调用 proc.kill() 期间创建子 SubX,这将不起作用
由于某种原因,经过多次尝试,其他解决方案对我不起作用,但这个解决方案可以!【参考方案7】:
当shell=True
时,shell 是子进程,命令是它的子进程。所以任何SIGTERM
或SIGKILL
都会杀死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 子进程的主要内容,如果未能解决你的问题,请参考以下文章
delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件
PySide Qt 脚本不能从 Spyder 启动,但可以从 shell 运行