【问题标题】:How to terminate a python subprocess launched with shell=True如何终止使用 shell=True 启动的 python 子进程
【发布时间】:2023-03-20 22:48:01
【问题描述】:

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

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 subprocess kill-process


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    这适用于 Pyhton 3.8.10 Win 10。

    我试过 subprocess.Popen shell tr​​ue

    1. subprocess.Popen.kill
    2. subprocess.Popen.terminate

    但这确实https://stackoverflow.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...
          • 我不建议使用 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 不是杀死子进程。
          猜你喜欢
          • 2019-03-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-16
          • 2017-12-12
          • 2010-12-10
          相关资源
          最近更新 更多