Terminate a gnome-terminal opened with subprocess
Asked Answered
G

3

5

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.

Gametophore answered 7/1, 2016 at 15:49 Comment(0)
P
6

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
Polymeric answered 9/1, 2016 at 6:54 Comment(5)
Is there a way to go about this without --disable-factory? I'm trying to make this optionally work with xterm, which does not have the --disable-factory option.Gametophore
@Shatnerz: I don't know how xterm works. You could ask a separate Stack Overflow question about xterm case specifically.Polymeric
Just an update: --disable-factory is no longer supported, at least as of version 3.28.2. I'm still interested in the answer to this question since this solution didn't work for me!Cyrano
@JamesPaulMason: the code in the answer works as is on my Ubuntu machine with GNOME Terminal 3.28.2.Polymeric
Just tried on two different systems. It worked on Ubuntu with gnome-terminal 3.18.3 but not RedHat with gnome-terminal 3.28.2 using VTE 0.52.2 +GNUTLS. ¯_(ツ)_/¯Cyrano
J
1

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')
Jen answered 7/1, 2016 at 18:50 Comment(9)
I tried exactly what you said in an ipython terminal. The opened terminal was not closed.Gametophore
I guess I found it. I edited the answer adding the sessions id and closing using os.killpg instead of os.killJen
So, this looks like progress. I can copy and past the code exactly. No terminal pops up. I assume it is destroyed immediately. However, I add a 3 second sleep, and the terminal is never destroyed, even at completion of the script. It looks like nothing happens when I try using the interactive python shellGametophore
Another option might be to use psutil. I will write another example in the answer so you can give a try!Jen
downvote. None of the solutions work with gnome-terminal. On my system gnome-terminal is a starter application that does not wait for the actual terminal to exit: it 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).Polymeric
Hi @j-f-sebastian, would you have any suggestion on how to get gnome-terminal-server pid in order to kill it?Jen
Hi @Shatners , eureka! Now it is working and I tested on my python 2.7 console on ubuntu 14.04Jen
I didn't say that gnome-terminal launches gnome-terminal-server (it is likely exists already). You should remove broken solutions (or indicate clearly what solutions are broken).Polymeric
Do you think it's clear now? Can you remove the down vote? Thanks for your help in this answerJen
S
0

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)
Sibelius answered 15/12, 2022 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.