Programatically open multiple GNOME Terminal tabs

Simon Sapin,

Once in a while, apticron tells me that I should do package upgrades on my server. Easy enough: ssh into the server and run sudo aptitude update && sudo aptitude safe-upgrade. That’s a bit too much typing, so I have an alias in the server’s .bashrc:

alias apt-upgrade="sudo aptitude update && sudo aptitude safe-upgrade"

And bash-completion does its magic with just apt-u<TAB>. Everything is well and good.

But now I have two Debian servers. When one has updates available, the other server probably has the same. Things can be parallelized by starting with the second server in a new shell while the first is still running. It’s not too bad, but this still is a sequence of actions that are always the same and repeated regularly. This means that it can, and should be automated.

Without any more suspense, the incantation to open from a script a new GNOME Terminal window with multiple tab and some script being run in each tab is:

gnome-terminal --tab -e command1 --tab -e command2 [...]

A few things to note here:

SSH has a few subtleties too:

ssh -t "sudo aptitude update && sudo aptitude safe-upgrade"

The .bashrc file is not sourced in non-interactive mode, that is when a command is given, so we can not use the alias. That’s okay since this line is gonna be in a script anyway. The quotes are there so that the && part is interpreted by the server and not locally. Finally, -t asks for a pseudo-tty to be allocated as in interactive mode. Without it, sudo only gets a “dumb” pipe for standard input and output and can not prevent the password from being echoed on the terminal.

Putting all this together is straightforward but one needs to be careful with quote escaping. However I want to be prepared for the day I have 3 or 30 servers, and things should be DRY anyway. Sticking with bash is doable, (think piping a for loop into xargs) but quote escaping gets really hairy. Trust me, I tried. Time to get a real programming language. Enter Python: short and sweet.

#!/usr/bin/env python
import subprocess

command = 'sudo aptitude update && sudo aptitude safe-upgrade'
terminal = ['gnome-terminal']
for host in ('server1', 'server2'):
    terminal.extend(['--tab', '-e', '''
        bash -c '
            echo "%(host)s$ %(command)s"
            ssh -t %(host)s "%(command)s"
    ''' % locals()])

(Find this code on github)

Two tricks here reduce the quote escaping by a level each: subprocess can take an actual list of argument instead of a space-separated string, and Python has triple-quoted strings that can contain unescaped quotes.

Bonus: it displays what command is being run on what server in each shell.

Link that to somewhere in your $PATH with a name that works well with bash-completion, and that’s pretty much as good as it gets.