Tell Me When It's Finished

A loaf of bread, fresh from the oven
Nathan Long

Engineer

Nathan Long

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!

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box