Tell Me When It's Finished

By: Nathan Long
A loaf of bread, fresh from the oven

Some commands take a while. When I’m running a large test suite, re-seeding a database or installing dependencies, I often switch away from the terminal window to do something else. But I like to know when the task is finished.

The simplest solution is to run two commands: the first to do the job, and the second to notify me. For example:

mix run priv/repo/seeds.exs; say "finished seeding"

( say is a MacOS command that reads aloud whatever text it’s given. On Linux, espeak works the same way. )

I also like to be told whether the command succeeded or failed. Assuming it sets the exit code properly, that’s easy to check:

some_command && say "success" || say "failure"

Gold-plating

The solution above is probably all you need. But in my own usage, I’ve made two small improvements.

First, I use this trick often enough that I wanted a shorthand. judge some_command is the name I chose.

Second, I wanted to be able to use it as an afterthought, like some_command; judge. For example, if I start running some_command and then realize it’s going to take a while, I can type judge and press enter, so that when the first command finishes, judge will tell me whether it succeeded or failed.

So in my shell configuration (.zshrc, since I use Zsh), I define a function like this:

# This is for bash-like shells; for a fish shell port, see
# https://github.com/iamvery/dotfiles/blob/master/.config/fish/functions/judge.fish
#
# Usage:
# - with args, `judge mix test`; runs `yay` or `boom`
#   depending on exit status of given command
# - without args, `mix test; judge`; runs `yay` or `boom`
#   depending on exit status of previous command
function judge() {
  last_exit_status=$?
  number_of_args=$#
  if [ $number_of_args -gt 0 ]
  then
    # - treat the args as a command to run
    # - $@ is all the args given
    # - `"$@"` makes sure that quoting is preserved;
    #     eg, if the command was `judge echo one "two three"`,
    #     `echo` will get two args, not three
    # - Once the expansion is done, the shell sees a bare
    #   command and runs it.
    "$@" && yay || boom
  else
    # No args given means no command to run, so check the exit
    # status of the last command and notify accordingly
    [ $last_exit_status -eq 0 ] && yay || boom
  fi
}

This function runs yay for successful commands and boom for failed ones.

I’ve defined yay and boom as shell scripts on my path. Each plays an audio clip, using a backgrounded command (ending with &) so that it immediately returns control to the shell. yay plays applause and boom plays an explosion sound. And each sets an appropriate exit code to allow further chaining via && or ||.

Here is yay:

#!/bin/bash
afplay ~/.dotfiles/other_files/sounds/yay.mp3 -v -0.2 &
exit 0

And here’s boom:

#!/bin/bash
afplay ~/.dotfiles/other_files/sounds/boom.mp3 -v 0.7 &
exit 1

Of course, these scripts could play a randomly-chosen sound file, display a visual notification, or anything else that would get your attention.

Happy coding!