While I was packaging notes, a note-taking command line program I wrote, for distribution via Homebrew, I ran into difficulties managing my PATH.

To make sure notes installed via brew install notes works the same as notes in my development repo, I installed notes via Homebrew. The executable I use daily is the one living in the keg in /usr/local/Cellar, which gets added to PATH via a symlink in /usr/local/bin.

However, if I want to make changes to the program, I have to add the development version of notes to my PATH, ahead of /usr/local/bin/notes, in order to test it. When I’m done I have to remove the development version from my PATH so that notes references /usr/local/bin/notes again. This can get really annoying.

To solve this problem, I wrote userbin, a shell function that creates or removes a symlink from $HOME/.local/bin to any executable. The function accepts just one argument: the path to the executable when creating the symlink, or its name when removing it. The syntax is forgiving and intuitive:

  • both userbin any/path/<exe> and userbin <exe> will remove $HOME/.local/bin/<exe>, if the symlink exists
  • if it doesn’t, userbin path/to/<exe> will create $HOME/.local/bin/<exe>, as long as path/to/<exe> is valid
  • multiple executables can be passed, and userbin will attempt to link or unlink all of them

This function depends on $HOME/.local/bin coming first in your PATH. This is as easy as adding PATH="${HOME}/.local/bin:${PATH}" to the end of your startup script.

userbin is one of various functions I define in ~/.helpers.sh, a script I source in .zshrc. It’s useful for anyone developing and testing a command line tool themselves. Here’s the source code:

function userbin(){
  bin="${HOME}/.local/bin"

  while test $# -gt 0; do
    exe=`basename $1`
    tgt="${bin}/${exe}"
    src="$(pwd)/$1"

    if [ -L $tgt ]; then
      rm $tgt
      echo "removed ${exe} from ${bin}"
      shift && continue
    fi

    if [ -f $src ]; then
      ln -s $src $tgt
      echo "linked ${src} to ${tgt}"
      shift && continue
    fi

    echo "the file ${src} doesn't exist"
    return 1
  done

  return 0
}