5

If you type this simple test command:

gnome-terminal -x bash -c "ls;sleep 3"

You will find that it returns immediately (the newly created terminal, of course, lingers for three seconds). This is in contrast to, say, rxvt (same command but with e).

If you want a blocking start, the historical consensus seems to have been to use --disable-factory. Unfortunately, this does not work anymore (tested 3.14.2).

So, how do I start the terminal in a non-asynchronous manner?

Bonus: konsole, lxterminal, and xfce4-terminal at least also have the same problem. Commands for those?

5 Answers5

4

I use a method that has some similarities to the answer of terdon (and was created with some help of him - Thank you @terdon for this!), but has a slightly different approach:

I create a temp file so that the child terminal can communicate with the parent terminal and tell it the PID of its corresponding bash instance. Then I let the parent terminal read the temp file and remember the PID, delete the file and continue by checking every 100ms (delay can be changed) whether the child terminal's bash instance is still running. If not, the terminal was closed either manually or because the command finished. Then the launching command finishes and the parent terminal is usable again.

Advantage of this approach:
All commands that will be launched (ls; sleep 3 or any replacement for those) are executed after the command that is responsible to allow detection of the terminal closing. So if the inner command hangs or you close the terminal manually before it ends, the mechanism is still working and will continue execution of the outer script instead of running into infinite loops.


The code as one-liner with debug output ("launched" after the child terminal window was opened, "terminated" after it was closed) and accuracy of 0.1 seconds is:

pidfile=$(mktemp); gnome-terminal -x bash -c "echo \$$>$pidfile; ls; sleep 3"; until [ -s $pidfile ]; do sleep 0.1; done; terminalpid=$(cat "$pidfile"); rm $pidfile; echo "launched"; while ps -p $terminalpid > /dev/null 2>&1; do sleep 0.1; done; echo "terminated"

Or without debug output:

pidfile=$(mktemp); gnome-terminal -x bash -c "echo \$$>$pidfile; ls; sleep 3"; until [ -s $pidfile ]; do sleep 0.1; done; terminalpid=$(cat "$pidfile"); rm $pidfile; while ps -p $terminalpid > /dev/null 2>&1; do sleep 0.1; done

Almost the same code but with more flexibility written as bash script:

#! /bin/bash

delay=0.1
pidfile=$(mktemp)

gnome-terminal -x bash -c "echo \$$>$pidfile; ls; sleep 3"

until [ -s $pidfile ] 
    do sleep $delay
done
terminalpid=$(cat "$pidfile")
rm $pidfile
echo "launched"
while ps -p $terminalpid > /dev/null 2>&1
    do sleep $delay
done
echo "terminated"

You may omit the echo "launched" and echo "terminated" lines of course, as well as you can change the delay=0.1 line to another delay between two checks of the terminal state (in seconds) if you need it to be more or less accurate.

To execute another custom command in the child terminal, replace the line

gnome-terminal -x bash -c "echo \$$>$pidfile; ls; sleep 3"

with the following (insert your command instead of the capitalized placeholder!)

gnome-terminal -x bash -c "echo \$$>$pidfile; INSERTYOURCOMMANDSHERE"
Byte Commander
  • 110,243
3

In their infinite wisdom, the GNOME devs decided to remove that option. Unfortunately, their wisdom did not extend to also updating their man page which still lists it. So, it looks like gnome-terminal will always be run int the background and the parent shell session will be returned to immediately. To get around this, you have a few options:

  1. Just use another terminal. I tried with xterm, rxvt and GNOME terminator, all of which worked as expected.

  2. Use some ugly hacks. gnome-terminal, when first run, launches /usr/lib/gnome-terminal/gnome-terminal-server. For some reason, this means that the process is finished as soon as you launched it. To illustrate:

    $ gnome-terminal  -x sh -c "ls;sleep 30" 
    [1] 5896
    $ jobs
    [1]+  Done                    gnome-terminal -x sh -c "ls;sleep 30"
    

    As you can see above, after launching it in the background, the launched job exits immediately. This means that my first thought of launching it in the background and then using $! to check whether it is still running won't work. This means you'll have to do something less elegant like creating a file:

    tmpfile=$(mktemp); gnome-terminal  -x sh -c "ls;sleep 30; rm $tmpfile" 
    while [ -e $tmpfile ] ; do :; done
    

    The commands above will i) create a temporary file (tmpfile=$(mktemp)); ii) launch gnome-terminal, telling it to delete $tmpfile when finished and iii) do nothing (:) as long as the temp file exists while [ -e $tmpfile ]. This will result in a terminal that waits until the process run by gnome-terminal has finished before continuing.

terdon
  • 104,119
2

The Ubuntu maintainers of the gnome-terminal package noticed this issue and created a wrapper script (in Ubuntu package gnome-terminal-3.14.2-0ubuntu3) to re-enable the --disable-factory option; however, the wrapper script doesn't work!

From the changelog http://changelogs.ubuntu.com/changelogs/pool/main/g/gnome-terminal/gnome-terminal_3.14.2-0ubuntu3/changelog:

gnome-terminal (3.14.2-0ubuntu3) vivid; urgency=medium

  • debian/gnome-terminal: Add a wrapper script to launch gnome-terminal with a different app-id when a user passes the now ignored --disable-factory option. This should restore compatibility with old launchers for users who are upgrading. [...]

I can't navigate the Ubuntu "Launchpad" (so much for open source) but the wrapper script can be found in https://launchpad.net/ubuntu/+archive/primary/+files/gnome-terminal_3.14.2-0ubuntu3.debian.tar.xz (called gnome-terminal.wrap).

The bug is that the gnome-terminal.wrap script is waiting on the wrong child process; it should be waiting on the terminal server, not the terminal client. The fix is to change the two methods server_appeared and spawn_terminal_server as follows:

    def server_appeared(self, con, name, owner):
        # start gnome-terminal now
        gt = Gio.Subprocess.new(['/usr/bin/gnome-terminal.real',
                                 '--app-id', name] +
                                self.args,
                                Gio.SubprocessFlags.NONE)
        # removed a line here: gt.wait_async(...)

    def spawn_terminal_server(self, name):
        ts = Gio.Subprocess.new(['/usr/lib/gnome-terminal/gnome-terminal-server',
                                 '--app-id',
                                 name],
                                Gio.SubprocessFlags.NONE)
        ts.wait_async(None, self.exit_loop, ts)

You can download the fixed file from: https://gist.github.com/ecatmur/00893506a23e828c6688.

I've notified the package maintainer so hopefully it should be fixed fairly soon.


Another interesting fact: gnome-terminal can be built with an alternative client called gterminal which has a --wait option which seems to do exactly what you want. However, unfortunately Ubuntu don't build or install it in their gnome-terminal package.

ecatmur
  • 123
0

I've been using newer gnome-terminal, and the behavior you've described for gnome-terminal appears to be the same for konsole, lxterm, and rxvt (tried all 3 ). So since OP has not answered any comments so far to clarify what he or she wants , I'm making assumption that OP wants to continue using the parent terminal without waiting for child terminal to finish.

That can be achieved with gnome-terminal &. If you want to avoid child terminal being closed when parent exits, use nohup gnome-terminal &. To avoid seeing error output in parent terminal use gnome-terminal 2> /dev/null & or nohup gnome-terminal 2> /dev/null & .

0

For people in Feb. 2017 < t < March 2018, who came to this site via google, the simple solution is:

gnome-terminal --disable-factory -e "cmd"

works and starts gnome-terminal in a synchronous/blocking manner as expected.

Tested under:

  • Ubuntu 16.04
  • gnome-terminal 3.18.3
Oscillon
  • 154