Nathan Grigg

Monitor long-running commands: capture return status

This is the first in a series of posts describing the system I built to monitor long-running commands.

For me, this started as a way to light a blink 1 LED at work when a long build completed, bringing me back to the more important task from whatever may have distracted me. After a recent post by Casey Liss, I’ve added watch notifications to the mix.

The problem, for me at least, is that I never know when a command is going to take long enough that I lose interest and want a notification, so appending a command at the end of the original is not an option:

build; notify

You could always run build, and then when you realize you want notified, type notify blindly, but any stray keys or sometimes even mouse scrolling might mess up the follow up command. (Or whatever long-running command you are using might be capturing stdin.)

What I really wanted was a way for any terminal window to wait on any other terminal window. And of course, it would be nice to be able to do something different depending on what the return value is.

The first step is to capture and log the return value of every command in every window. Not long ago, this would have seemed ludicrous, but with SSDs, it really isn’t a big deal to write to disk once per command.

Unique window identifiers

To be able to tell one command from another, you need some kind of an identifier for each window. At work, I always use tmux, which calls windows panes, and gives each pane an incrementing identifier, stored in the TMUX_PANE variable. At home, I use iTerm2, which sets the TERM_SESSION_ID variable. This variable actually has two parts separated by a colon, and the unique id is in the second part, which is accessible using parameter subtitution as ${TERM_SESSION_ID#*:}.

Log the return value

Both zsh and bash can run an arbitrary command right before drawing the prompt, that is, right after any command finishes. This is a perfect place to capture and log the return value.

In zsh, this is precmd, so you can define something like this in your zshrc (using tmux pane):

precmd() {
    echo "$?" > "$HOME/.local/logs/return-$TMUX_PANE"

The variable $? holds the return value of the last command.

In bash, this is PROMPT_COMMAND, so you can put this in your bashrc (using iTerm window id):

PROMPT_COMMAND='echo "$?" > "$HOME/.local/logs/return-${TERM_SESSION_ID#*:}"'

In the next post, I’ll show you how to use this logged value to wait for a command to finish.