linux – How to terminate a python subprocess launched with shell=True

The Question :

337 people think this question is useful

I’m launching a subprocess with the following command:

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

However, when I try to kill using:

p.terminate()

or

p.kill()

The command keeps running in the background, so I was wondering how can I actually terminate the process.

Note that when I run the command with:

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

It does terminate successfully when issuing the p.terminate().

The Question Comments :
  • What does your cmd look like? It might contain a command which triggers several processes to be started. So it’s not clear which process you talk about.
  • related: Python: how to kill child process(es) when parent dies?
  • does not having shell=True make a big difference?

The Answer 1

435 people think this answer is useful

Use a process group so as to enable sending a signal to all the process in the groups. For that, you should attach a session id to the parent process of the spawned/child processes, which is a shell in your case. This will make it the group leader of the processes. So now, when a signal is sent to the process group leader, it’s transmitted to all of the child processes of this group.

Here’s the code:

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

The Answer 2

97 people think this answer is useful
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill() ends up killing the shell process and cmd is still running.

I found a convenient fix this by:

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

This will cause cmd to inherit the shell process, instead of having the shell launch a child process, which does not get killed. p.pid will be the id of your cmd process then.

p.kill() should work.

I don’t know what effect this will have on your pipe though.

The Answer 3

59 people think this answer is useful

If you can use psutil, then this works perfectly:

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)

The Answer 4

29 people think this answer is useful

I could do it using

from subprocess import Popen

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

it killed the cmd.exe and the program that i gave the command for.

(On Windows)

The Answer 5

15 people think this answer is useful

When shell=True the shell is the child process, and the commands are its children. So any SIGTERM or SIGKILL will kill the shell but not its child processes, and I don’t remember a good way to do it. The best way I can think of is to use shell=False, otherwise when you kill the parent shell process, it will leave a defunct shell process.

The Answer 6

10 people think this answer is useful

None of this answers worked for me so Im leaving the code that did work. In my case even after killing the process with .kill() and getting a .poll() return code the process didn’t terminate.

Following the subprocess.Popen documentation:

“…in order to cleanup properly a well-behaved application should kill the child process and finish communication…”

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

In my case I was missing the proc.communicate() after calling proc.kill(). This cleans the process stdin, stdout … and does terminate the process.

The Answer 7

5 people think this answer is useful

As Sai said, the shell is the child, so signals are intercepted by it — best way I’ve found is to use shell=False and use shlex to split the command line:

if isinstance(command, unicode):
    cmd = command.encode('utf8')
args = shlex.split(cmd)

p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Then p.kill() and p.terminate() should work how you expect.

The Answer 8

2 people think this answer is useful

Send the signal to all the processes in group

    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)

The Answer 9

2 people think this answer is useful

what i feel like we could use:

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

os.killpg(os.getpgid(pro.pid), signal.SIGINT)

this will not kill all your task but the process with the p.pid

The Answer 10

0 people think this answer is useful

I know this is an old question but this may help someone looking for a different method. This is what I use on windows to kill processes that I’ve called.

si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.call(["taskkill", "/IM", "robocopy.exe", "/T", "/F"], startupinfo=si)

/IM is the image name, you can also do /PID if you want. /T kills the process as well as the child processes. /F force terminates it. si, as I have it set, is how you do this without showing a CMD window. This code is used in python 3.

The Answer 11

0 people think this answer is useful

There is a very simple way for Python 3.5 or + (Actually tested on 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)

Then, your code may crash at some point if you have a keyboard input detection, or sth like this. In this case, on the line of code/function where the error is given, just use:

try:
    FailingCode #here goes the code which is raising KeyboardInterrupt
except KeyboardInterrupt:
    pass

What this code is doing is just sending a “CTRL+C” signal to the running process, what will cause the process to get killed.

The Answer 12

-2 people think this answer is useful

I have not seen this mentioned here, so I am putting it out there in case someone needs it. If all you want to do is to make sure that your subprocess terminates successfully, you could put it in a context manager. For example, I wanted my standard printer to print an out image and using the context manager ensured that the subprocess terminated.

import subprocess

with open(filename,'rb') as f:
    img=f.read()
with subprocess.Popen("/usr/bin/lpr", stdin=subprocess.PIPE) as lpr:
    lpr.stdin.write(img)
print('Printed image...')

I believe this method is also cross-platform.

The Answer 13

-2 people think this answer is useful

Although it is an old question, it has a high google rank. so I decided to post an answer with the new method someone can use in python 3 to manage this easily and with confidence. as of python 3.5 you there is a new method added to subprocess package called run().

As the documentation says:

It is the recommended approach to invoking sub processes for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

The subprocess.run():

Runs a command described by args. Wait for the command to complete, then return a CompletedProcess instance.

for example one can run this snippet within a python console:

>>> subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

P.S. In case of the OP’s specific question, I wasn’t able to reproduce his problem. commands I run with popen() are terminating properly.

Add a Comment