Terminate a gnome-terminal opened with subprocess

Question:

Using subprocess and the command ‘gnome-terminal -e bash‘ I can open up a gnome-terminal as desired (and have it stick around). This is done with either

p=subprocess.Popen(['gnome-terminal', '-e', 'bash'])

or

p=subprocess.Popen(['gnome-terminal -e bash'], shell=True)

but I cannot close the terminal using p.terminate() or p.kill(). From what I understand, this is a little trickier when using shell=True but I did not expect to run into problems otherwise.

Asked By: Shatnerz

||

Answers:

You should be able to do this workaround:

  • get the process id
  • kill the process

Working Solution: Close gnome-terminal-server

As suggested by @j-f-sebastian in the comment, gnome-terminal

just sends the request (to gnome-terminal-server) to start a new terminal and exits immediately — there is nothing to kill the process is already dead (and newly created processes are not descendants:  the new bash process is a child of gnome-terminal-server, not gnome-terminal).

import subprocess
import os, signal
import time

p=subprocess.Popen(['gnome-terminal -e bash'], stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
print "this is going to be closed in 3 sec"
time.sleep(3)
# this line returns the list of bash instances pid as string
bash_pids = subprocess.check_output(["pidof", "bash"])
# I get the last instance opened
pid_to_kill = bash_pids.split(" ")[0]
os.kill(int(pid_to_kill), signal.SIGTERM)

My solution is following this logic:

  • run gnome-terminal
  • get the latest bash instance opened process id
  • kill this process id

Broken solutions

These solutions might work in simpler cases:

Solution 1

import subprocess
import os, signal

p=subprocess.Popen(['gnome-terminal -e bash'], shell=True)
p_pid = p.pid  # get the process id
os.kill(p_pid, signal.SIGKILL)

In order to choose the appropriate method of signal to pass instead of SIGKILL you can refer the signal documentation. E.g.

On Windows, signal() can only be called with SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, or SIGTERM

For Unix you have a quite extensive list of method to call.

To have a better overview about os.kill, you can refer its documentation.

Solution 2

An alternative method useful for Unix could be:

import subprocess
import os, signal

p=subprocess.Popen(['gnome-terminal -e bash'], stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), signal.SIGTERM)

It seems that your process is opening child process that prevent the parent to be close. Adding a session id to your parent process, you should be able to fix it.

Solution 3

import subprocess, psutil

def kill(p_pid):
    process = psutil.Process(p_pid)
    for proc in process.get_children(recursive=True):
        proc.kill()
    process.kill()

p = subprocess.Popen(['gnome-terminal -e bash'], shell=True)
try:
    p.wait(timeout=3)
except subprocess.TimeoutExpired:
    kill(p.pid)

This solution requires psutil.

Solution 4

According to askubuntu, it seems that the best way to close a gnome terminal instance would be to execute a bash command like:

killall -s {signal} gnome-terminal

where {signal} simulates Alt + F4.

You can try to do it using [pexpect]:

p = pexpect.spawn(your_cmd_here)
p.send('^F4')
Answered By: mabe02

To terminate a terminal and its children (in the same process group):

#!/usr/bin/env python
import os
import signal
import subprocess

p = subprocess.Popen(['gnome-terminal', '--disable-factory', '-e', 'bash'],
                     preexec_fn=os.setpgrp)
# do something here...
os.killpg(p.pid, signal.SIGINT)
  • --disable-factory is used to avoid re-using an active terminal so that we can kill newly created terminal via the subprocess handle
  • os.setpgrp puts gnome-terminal in its own process group so that os.killpg() could be used to send signal to this group
Answered By: jfs

I wanted to add this snippet for anyone who is running on Linux Ubuntu and trying to open a subprocess, run a script, and terminate it after a time.wait().

I found a litany of solutions that would open a window, but not close it. Or a solution would open a window, and close it, but wouldn’t run the script inside the terminal.

There was no exact answer so I had to hack together several solutions, as I am a novice when it comes t subprocess/shell.

This snippet was able to open a subprocess, run the script, and when 10 seconds had passed the subprocess was terminated. Again, this was built ofn the shoulders of giants. I hope this saves someone time; cheers.

            import os
            import signal
            import subprocess
            import time

            command = 'python3 Rmonitor.py'
            p = subprocess.Popen(['gnome-terminal','--disable-factory', '--', 'bash', '-c', command],preexec_fn=os.setpgrp)
            time.sleep(10)
            os.killpg(p.pid, signal.SIGINT)
Answered By: Richard Pennington
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.