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

How to terminate a python subprocess launched with shell=True

提问人:user175259 提问时间:1/25/2011 最后编辑:Piotr Dobrogostuser175259 更新时间:5/23/2022 访问量:491532

问:

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

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

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

p.terminate()

p.kill()

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

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

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

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

python linux 子进程 kill-process

评论

0赞 Robert Siemer 11/27/2014
你长什么样子?它可能包含一个命令,该命令触发要启动的多个进程。所以不清楚你说的是哪个过程。cmd
2赞 jfs 1/23/2015
相关新闻: Python:当父母死亡时如何杀死子进程?
3赞 Charlie Parker 3/8/2019
没有太大区别吗?shell=True

答:

20赞 Sai Venkat 1/25/2011 #1

当 shell 是子进程,命令是其子进程时。因此,任何 or 都会杀死 shell,但不会杀死它的子进程,我不记得有什么好方法可以做到这一点。 我能想到的最好的方法是使用 ,否则当你杀死父 shell 进程时,它会留下一个失效的 shell 进程。shell=TrueSIGTERMSIGKILLshell=False

评论

0赞 Keivan 7/22/2022
为了避免僵尸进程,可以终止进程,然后等待,然后轮询进程以确保它们已终止,如以下stackoverflow答案中的“@SomeOne Maybe”所述:stackoverflow.com/questions/2760652/...
9赞 Matt Billenstein 1/25/2011 #2

正如 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赞 user175259 1/26/2011
就我而言,鉴于cmd是“cd path&&zsync等”,它并没有真正的帮助。所以这实际上使命令失败!
5赞 Matt Billenstein 1/26/2011
使用绝对路径而不是更改目录...(可选)os.chdir(...) 到该目录...
0赞 Jonathon Reinhart 8/17/2015
内置了更改子进程的工作目录的功能。只需将参数传递给 Popencwd
0赞 hungryWolf 1/25/2017
我使用了 shlex,但问题仍然存在,kill 不是杀死子进程。
538赞 mouad 1/25/2011 #3

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

代码如下:

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

评论

8赞 Piotr Dobrogost 10/19/2012
这与此有什么关系?subprocess.CREATE_NEW_PROCESS_GROUP
15赞 hwjp 6/10/2013
我们的测试显示 setsid != setpgid,并且 os.pgkill 仅终止仍具有相同进程组 ID 的子进程。 已更改进程组的进程不会被终止,即使它们可能仍具有相同的会话 ID...
7赞 parasietje 1/12/2015
我不建议做os.setsid(),因为它也有其他效果。除其他外,它断开了控制 TTY 的连接,并使新进程成为进程组的领导者。查看 win.tue.nl/~aeb/linux/lk/lk-10.html
11赞 HelloGoodbye 7/7/2016
您将如何在 Windows 中执行此操作? 仅在 *nix 系统上可用。setsid
137赞 Bryant Hansen 10/31/2012 #4
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 启动子进程,该子进程不会被终止。 将是您的 cmd 进程的 ID。p.pid

p.kill()应该工作。

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

评论

2赞 MarSoft 8/24/2015
*nix 的漂亮而轻巧的解决方案,谢谢!适用于 Linux,也应该适用于 Mac。
0赞 Nicolinux 8/5/2016
非常好的解决方案。如果你的 cmd 恰好是其他东西的 shell 脚本包装器,那么也要用 exec 调用最终的二进制文件,以便只有一个子进程。
2赞 Sergiy Kolodyazhnyy 8/25/2016
这很漂亮。我一直在试图弄清楚如何在 Ubuntu 上为每个工作区生成和杀死一个子进程。这个答案帮助了我。希望我能不止一次地投票
2赞 gnr 12/2/2017
如果在 CMD 中使用分号,则这不起作用
13赞 DarkLight 9/12/2019
@speedyrazor - 不适用于 Windows10。我认为应该清楚地标记特定的答案。
41赞 SPratap 7/12/2013 #5

我可以用

from subprocess import Popen

process = Popen(command, shell=True)
Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))

它杀死了我发出命令的程序。cmd.exe

(在 Windows 上)

评论

0赞 zvi 1/9/2020
或者使用进程名称:,如果没有,可以从参数中获取。Popen("TASKKILL /F /IM " + process_name)command
100赞 Jovik 8/5/2014 #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)

评论

3赞 d33tah 7/14/2015
AttributeError: 'Process' object has no attribute 'get_children为。pip install psutil
4赞 Godsmith 9/18/2015
我认为get_children()应该是children()。但它在 Windows 上对我不起作用,该过程仍然存在。
3赞 Jovik 9/22/2015
@Godsmith - psutil API 已更改,您是对的:children() 与 get_children() 过去做同样的事情。如果它在 Windows 上不起作用,那么您可能需要在 GitHub 中创建一个 bug 票证
0赞 Smak 12/24/2020
如果子 X 在为子 A 调用 proc.kill() 期间创建子 SubX,则这不起作用
0赞 roshambo 8/5/2021
出于某种原因,经过多次尝试,其他解决方案对我不起作用,但这个解决方案确实如此!
18赞 epinal 3/1/2018 #7

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

按照文档subprocess.Popen

"...为了正确清理,一个行为良好的应用程序应该杀死子进程并完成通信......”

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

就我而言,我错过了电话后。这清理了过程 stdin、stdout ...并终止该进程。proc.communicate()proc.kill()

评论

0赞 user9869932 12/7/2018
这个解决方案在 linux 和 python 2.7 中对我不起作用
0赞 epinal 12/11/2018
@xyz 它在 Linux 和 python 3.5 中确实对我有用。查看 python 2.7 的文档
0赞 user9869932 12/11/2018
@espinal,谢谢,是的。这可能是 linux 问题。它是在 Raspberry 3 上运行的 Raspbian linux
0赞 mhanuel 7/22/2022
@mouad和布莱恩特的答案在我的情况下是可以的,如果你按照你所说的在呼叫杀戮后使用沟通。谢谢。
0赞 Ayan Mitra 11/11/2022
这在 Linux 中对我有用
2赞 rogers.wang 11/5/2018 #8

将信号发送到组中的所有进程

    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)
3赞 Martí Climent 11/28/2020 #9

有一种非常简单的方法可以用于 or +(实际上在Python 3.5Python 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

这段代码所做的只是向正在运行的进程发送一个“+”信号,这将导致进程被终止。CTRLC

评论

1赞 Jeff Wright 6/30/2021
注意:这是特定于 Wndows 的。在 Mac 或 Linux 实现中没有定义 CTRL_C_EVENT。可以在此处找到一些替代代码(我尚未测试)。signal
1赞 Muhammad Wahaj Murtaza 1/31/2022 #10

对我有用的解决方案

if os.name == 'nt':  # windows
    subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))
else:
    os.kill(process.pid, signal.SIGTERM)
0赞 Orsiris de Jong 5/23/2022 #11

完整的解决方案,将在达到超时或通过回调函数的特定条件时终止正在运行的进程(包括子树)。 在撰写本文时,从 Python 2.7 到 3.10 都可以在 Windows 和 Linux 上运行。

安装方式pip install command_runner

超时示例:

from command_runner import command_runner

# Kills ping after 2 seconds
exit_code, output = command_runner('ping 127.0.0.1', shell=True, timeout=2)

具体情况示例: 如果当前系统时间秒数字> 5,我们将停止 ping

from time import time
from command_runner import command_runner

def my_condition():
    # Arbitrary condition for demo
    return True if int(str(int(time()))[-1]) > 5

# Calls my_condition() every second (check_interval) and kills ping if my_condition() returns True
exit_code, output = command_runner('ping 127.0.0.1', shell=True, stop_on=my_condition, check_interval=1)