QR Code contains TinyURL of this article.Are We There Yet?

(get a system notification when a long-running shell task is complete and the terminal window does not have focus)

completed shell task notification

I do a heck of a lot of work from the command line. My MacBook is a fast machine1 and I make extensive use of parallelisation. Even so, some tasks take considerable time to complete. That’s fine but, whilst waiting for a long-running process, I will generally switch to another window to perform some other operation. Then I end up doing the command+tab dance in order to monitor the progress of my shell task.

There has to be a better way?

Possible Solutions

№ 1: Make all tools fast and develop a more zen-like patience. Maybe that would work in a parallel universe.

№ 2: Create a suitable wrapper around the terminal-notifier RubyGem, then append ; notifyme2 to long running commands. Googling suggests this to be the most common solution. However, this approach has problems:

  • You have to manually append the command… it’s easy to forget;
  • You don’t necessarily know if a given command is going to take a long time (hello git pull on a slow network after weeks of not working on a project);
  • The notification fires even when your terminal is in focus and that’s annoying because you can already see the command has finished.

№ 3: Use iTerm2 triggers to send an OS X notification when it finds a match of a sub-string from your shell’s PS1 prompt. Closer, but still suffers from the unwanted notifications issue.

№ 4: THE WINNER. Build a script that can figure out if your terminal is in the foreground and only send a notification in the opposite case.

Terminal Watcher

Open a terminal window and enter the following command:

sudo touch /usr/local/bin/notifyme3

then enter:

nano /usr/local/bin/notifyme

which will open an empty nano editor.

Copy and paste in the following AppleScript code:4

#!/usr/bin/env osascript

on run argv
  tell application "System Events"
    set frontApp to name of first application process whose frontmost is true
    if frontApp is not "iTerm2" then
      set myCommand to item 1 of argv
      set myVerb to first word of myCommand
      set notifTitle to "Terminal Watcher"
      set notifBody to "'" & myVerb & "' process is complete."
      set errorCode to item 2 of argv
      if errorCode is not "0"
        set notifBody to "'" & myVerb & "' process failed with error code: " & errorCode
      end if
      display notification notifBody with title notifTitle
    end if
  end tell
end run

Press control+O to save and control+X to exit the nano editor.

Make the AppleScript executable:

sudo chmod a+x /usr/local/bin/notifyme

Load your .zshrc (or .bashrc) file into the text editor:

nano ~/.zshrc

and append the following function to the bottom:

function f_notifyme {
  LAST_EXIT_CODE=$?
  CMD=$(fc -ln -1)
  # No point in waiting for the command to complete
  notifyme "$CMD" "$LAST_EXIT_CODE" &
}

Again, press control+O to save and control+X to exit the nano editor.

Finally, back in the shell, run the command:

source .zshrc5

That’s it. From now on, all your long-running shell commands will notify you upon completion — successful or otherwise — when your terminal window does not have focus.

Credit

Adapted (and almost copied verbatim) from Alex Kotliarskyi’s original work.

  1. Of course, it’s all relative↩︎

  2. Or whatever you call your wrapper with. ↩︎

  3. OS X will most likely prompt you for your password, this is normal. ↩︎

  4. If you use Terminal.app rather than iTerm, then replace the string iTerm2 with Terminal in the AppleScript. ↩︎

  5. Or source .bashrc if you use Bash↩︎