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?

geometrian
  • 225
  • 3
  • 9
  • 1
    What exactly you're trying to achieve ? Start terminal , let the programs finish, and then use the shell ? – Sergiy Kolodyazhnyy May 22 '15 at 15:15
  • The answer you linked is also from 2012, I'd say it's a little dated – Sergiy Kolodyazhnyy May 22 '15 at 15:16
  • 1
    Is `non-asynchronous` = `synchronous`? – A.B. May 22 '15 at 15:16
  • So you want that the command you use opens a new terminal window, executes the `ls` command, waits 3 seconds, then closes the terminal window _and returns control to the first terminal window __now___, so that you can't continue in the first window, while the second one is open. Correct? – Byte Commander May 22 '15 at 15:29
  • @A.B. `rxvt` does not immediately give the control back to the calling terminal, unlike `gnome-terminal` does, that's correct. I just tried it. The question is not about what happens in the second terminal, but what happens in the first or better when does the command in the first terminal return. – Byte Commander May 22 '15 at 15:43
  • @ByteCommander, yes; exactly.¶ Serg: indeed it is dated. That's the point. – geometrian May 22 '15 at 16:22
  • Did you try this? `gnome-terminal -x bash -c "ls; while :; do sleep 3; done"` – 0x2b3bfa0 May 22 '15 at 17:58
  • @Helio That does not help at all. I don't see why you're adding a while-loop (that does not end) to the inner command. The problem is that the outer command (launching a gnome-terminal) returns immediately after the window opened and not after the passed command argument (bash -c ...) finished. – Byte Commander May 22 '15 at 18:32
  • Did you try this? `gnome-terminal --disable-factory -x bash -c "ls;sleep 3"` – 0x2b3bfa0 May 22 '15 at 18:58
  • 1
    @Helio As mentioned in the question, `--disable-factory` does not work any more in the current version, although it is still listed in the manpage. Sorry. – Byte Commander May 22 '15 at 19:18
  • @ByteCommander: But it works for me on 3.6.2 – 0x2b3bfa0 May 22 '15 at 19:45
  • @Helio It does not work any more for me (and the OP) on gnome-terminal 3.14.2 on Ubuntu 15.04 – Byte Commander May 22 '15 at 19:47
  • @ByteCommander: Ok, now I'll shut up **;-)**. Your answer is the best (AFAIK). – 0x2b3bfa0 May 22 '15 at 20:05
  • Which version of `xce4-terminal`? `xfce4-terminal --disable-server` works as expected for me (using 0.6.3, Arch Linux - as vanilla as they come). – muru May 23 '15 at 12:04

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
  • 105,631
  • 46
  • 284
  • 425
  • I like that this is safe(r) than @terdon's current answer, which is similar. I don't suppose Bash has a thread yield one could use instead of `sleep`? – geometrian May 22 '15 at 19:51
  • Not that I knew one... But as it supports floating point arguments so that you can set short delays like 0.1s, this is no big problem, I would say. – Byte Commander May 22 '15 at 19:53
  • I agree, but if such a thing exists, it would have ~same latency as the scheduler (probably less than whatever delay you might reasonably put) and use less CPU than busy looping like in terdon's answer. – geometrian May 22 '15 at 19:57
  • I've forked it into its [own question](http://stackoverflow.com/q/30405121/688624). – geometrian May 22 '15 at 20:09
  • So as you now have a set of working answers (and forked the subquestion about yielding to SO), you should consider accepting / voting on them... – Byte Commander May 22 '15 at 20:20
  • I am aware. You, terdon, and ecatmur all have +1s, and after some more time has elapsed, I will accept the best. Currently yours. – geometrian May 22 '15 at 20:23
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
  • 98,183
  • 15
  • 197
  • 293
  • 1
    Your command is good and short, but it has one big disadvantage! If I close the child terminal window or otherwise terminate it so that your `rm $tmpfile` is not reached, the whole script will run into an infinite loop and needs to be canceled with Ctrl-C. – Byte Commander May 22 '15 at 18:45
  • Re: 1, indeed, but my application is providing compatibility, not demanding it (my application actually also explicitly supports `xterm`, `rxvt`, and `terminator`, among others, already). Re: 2, +1 this is a neat idea, and it forms the basis of the accepted answer. Though simpler, I think waiting on the process ID is a better and safe(r) idea. Thanks! – geometrian May 23 '15 at 02:35
  • @imallett yes, this approach could fail if you force close the terminal before the process is finished. What I don't understand is why in the world you need a terminal in the first place? Why not just run the commands you need to run by themselves? Why do you need to open 2 separate terminals? I get the feeling this might be an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – terdon May 23 '15 at 11:54
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
  • 4
  • +1 for finding the bug, suggesting the fix, and pushing it upstream. Unfortunately, my application needs to work with vanilla builds and be backward compatible (and in any case it doesn't solve the problem for `lxterminal` &tc., which I was unofficially also asking about). – geometrian May 22 '15 at 19:49
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 & .

Sergiy Kolodyazhnyy
  • 103,293
  • 19
  • 273
  • 492
  • Thanks for the answer, but I actually want precisely the opposite. On my system, `gnome-terminal` is implemented as a Python script that opens the terminal asynchronously already. `xterm` might be basic enough to reproduce the behavior I want. – geometrian May 22 '15 at 16:25
  • So what you want is for parent terminal to wait for child terminal finish job first ? – Sergiy Kolodyazhnyy May 22 '15 at 16:34
  • I'd say run ( gnome-terminal -x bash -c "ls; sleep 3" && echo done ) or something like that – Sergiy Kolodyazhnyy May 22 '15 at 16:37
  • Yes. Unfortunately that command doesn't have the desired effect since `gnome-terminal` starts a terminal asynchronously, returning immediately. – geometrian May 22 '15 at 19:23
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
  • 1
  • 9
  • **3.22.1:** `Failed to parse arguments: Option "--disable-factory" is no longer supported in this version of gnome-terminal` – stevesliva Mar 19 '18 at 18:29