Wednesday, May 16, 2012

Shell Trick: Background Launch

The Issue

Here is the problem.  It is not a big one, but it was quite an annoyance for me.  I want to launch a command, but I do not want it to block my command line, and I do not want it to pollute my terminal screen with all kinds of output.

The typical example for me is the gimp photo editor.  I use it infrequently so I do not give it precious desktop real estate with its own launcher.  What I usually do is bring up whichever gnome-terminal is active and launch it from the command line in the background.
> gimp &

Of course I launch it in the background because I want the command prompt to return in case I want to do other command line operations before I exit gimp.  The the problem with this is that gimp (like a bunch of other native GUI applications) produces a bunch of warning messages that I just do not want to see in my terminal.

(gimp:12691): GLib-WARNING **: /build/buildd/glib2.0-2.30.0/./glib/goption.c:2168: ignoring no-arg, optional-arg or filename flags (8) on option of type 0

(gimp:12691): Gtk-CRITICAL **: _gtk_file_chooser_entry_set_file_part: assertion `GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry)' failed

(gimp:12691): GLib-GObject-WARNING **: invalid cast from `GimpView' to `GtkImage'

(gimp:12691): Gimp-Widgets-WARNING **: gimp_dialog_factory_dispose: stale non-toplevel entries in factory->open_dialogs
Yes, these are just ignorable warnings, but do not want to see them. Perhaps I have historical output in that screen that I do not want scrolled away. More importantly, I do not want warnings to spontaneously appear while I type in that terminal.

The Non-Solution

So I could do Alt-F2 to run a command in an invisible, implicit terminal. There are two big problems with this.
  1. It is not a bash shell so I do get command completion, command history, or line editing.
  2. It probably does not have my $PATH customizations, so many of the commands I want to reference will be inaccessible.

The Solution

So I wrote a simple bash shell function to do the launching of the command, put it in the background, and send all of its output to a log file.  (I could have sent output to /dev/null to make it completely silent, but the log file does not consume much resources and it does come in handy if a post-mortem is needed.)
# Launch a command in the background, with all output saved to a log file.
# This is useful when verbose output is expected from the command and you
# do not want to clutter the terminal that launched it.
bglaunch () {
    if [[ $# != 0 ]]; then
        # The log name will be the command name + ".log"
        logname=$(basename $1)

        # Build the full path name of the log file.
        if [ -d "${SCRATCH_LOG_DIR}" ]; then
            # If $SCRATCH_LOG_DIR is defined and exists, put the log there.
            logfile="${SCRATCH_LOG_DIR}/${logname}.log"
        else
            # Otherwise put it in [/var/tmp] and prepend it with $USER.
            logfile="/var/tmp/${USER}-${logname}.log"
        fi

        printf "Output to [%s]\n" "${logfile}"
        eval "$@ > ${logfile} 2>&1 &"
    fi
}
Note that this snippet should be placed in your ~/.bashrc or similar appropriate location.

So now I simply do the following.
> bglaunch gimp
Output to [/home/johndoe/var/log/gimp.log]
[2] 14275
I get exactly what I want.
  • The command is launched in the background so the command line is immediately returned.
  • Any arguments and switches can be given to the command without confusion or conflict.
  • Wildcards can be used to expand filenames in the arguments to the command.
  • No further output of the command show up on the screen.
  • I take advantage of command completion to write out the full command name, then go the beginning of the line and prepend "bglaunch" to the line.

Detail Notes

The actual location of the output log is configurable.  It allows the user to places all logs in a directory that is specific to that user.  Otherwise, all the logs go to /var/tmp and get prepended by the username so that different users do not conflict with each other.  As shown in the output above, this additional definition was declared in .bashrc.
> export SCRATCH_LOG_DIR="${HOME}/var/log"