Zsh config

Get my shell behaving

Zsh is my main shell. It’s sufficiently compatible with bash that I have yet to do anything interesting with it.


Loaded for all sessions.





Support functions

Mostly path manipulation functions, at least until I find the inevitable zsh plugin.

  # Remove $2 from path with name $1, e.g.
  #   remove_from_path PATH ~/bin
  #   remove_from_path PYTHONPATH ~/lib/python2.7/site-packages
  function remove_from_path() {
    local a
    local p
    local s
    local r
    eval "p=\$$1"  # get value of specified path
    a=( ${(s/:/)p} )  # turn it into an array
    # return if $2 isn't in path
    if [[ ${a[(i)${2}]} -gt ${#a} ]] && return
    # rebuild path from elements not matching $2
    for s in $a; do
      if [[ ! $s == $2 ]]; then
        [[ -z "$r" ]] && r=$s || r="$r:$s"
    eval $1="$r"

  # Add path to start of named path, removing any occurences
  # already in it, e.g.
  #   prepend_path PATH ~/bin
  #   prepend_path PYTHONPATH ~/my-py-stuff
  function prepend_path() {
    # Exit if directory doesn't exit
    [[ ! -d "$2" ]] && return
    local p
    remove_from_path "$1" "$2"
    eval "p=\$$1"
    eval export $1="$2:$p"

  # As above, but add to end of path
  function append_path() {
    # Exit if directory doesn't exit
    [[ ! -d "$2" ]] && return
    local p
    remove_from_path "$1" "$2"
    eval "p=\$$1"
    eval export $1="$p:$2"

  function addpath() {
    prepend_path PATH $1

file_info () {
    # file_info: function to display file information

    if [[ -e $1 ]]; then
        echo -e "\nFile Type:"
        file $1
        echo -e "\nFile Status:"
        gstat $1
        echo "$FUNCNAME: usage $FUNCNAME file" >&2
        return 1

Base environment variables


Set base path

Been burned so many times — not being able to find a thing after I installed it — that I started setting $PATH myself a while back.


Define editor

Still mostly Neovim for quick edits.

export EDITOR="nvim"


I like pretty colors.

export CLICOLOR=1

    base00=bright_yellow, on_base00=on_bright_yellow,\
    base01=bright_green,  on_base01=on_bright_green, \
    base02=black,         on_base02=on_black,        \
    base03=bright_black,  on_base03=on_bright_black, \
    base0=bright_blue,    on_base0=on_bright_blue,   \
    base1=bright_cyan,    on_base1=on_bright_cyan,   \
    base2=white,          on_base2=on_white,         \
    base3=bright_white,   on_base3=on_bright_white,  \
    orange=bright_red,    on_orange=on_bright_red,   \

Add home bin

Set $PATH so it includes my home bin if it exists

if [ -d "$HOME/bin" ] ; then
    addpath "$HOME/bin"

Early local shell initialization

For stuff specific to a particular work environment. For defining variables and functions that may be needed by later processes.

if [ -f "$HOME/.zshenv_local_before" ]; then
    source "$HOME/.zshenv_local_before"


Homebrew works well enough on macOS and Linux that it’s become my default package manager. Of course, Arch has AUR, so there’s not as much need there.

  if [ -d "/home/linuxbrew" ] ; then
      # For Homebrew on Linux
      # Output to `/home/linuxbrew/.linuxbrew/bin/brew shellenv`
      eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)

  if which brew &> /dev/null; then
      export BREW_PREFIX=`brew --prefix`
      if [ -f "$BREW_PREFIX/etc/bash_completion" ]; then
      . "$BREW_PREFIX/etc/bash_completion.d/git-completion.bash"
      . "$BREW_PREFIX/etc/bash_completion.d/git-prompt.sh"
      . "$BREW_PREFIX/etc/bash_completion"

      if [ -f "$BREW_PYTHON" ]; then
          addpath "$BREW_PYTHON/bin"
          export LDFLAGS="-L$BREW_PYTHON/lib"
          export CPPFLAGS="-I$BREW_PYTHON/include"
          export PKG_CONFIG_PATH="$BREW_PYTHON/lib/pkgconfig"

      export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)"

Programming environments and language managers

plenv, pyenv, and friends. I have issues.


Enable go dev

I don’t need a go version manager, but a little environment and path manipulation is nice when it’s needed.

  export GOPATH="$HOME/go"
  if [ -d "/usr/local/go" ]; then
      addpath "/usr/local/go/bin"

  if [ -d "$GOPATH" ]; then
      addpath "$GOPATH/bin"

Enable pyenv

Pyenv is all sorts of headache really, but it’s the easiest way to get a fresh python.

I usually end up installing a few pyenv-specific packages before it all starts to make sense.

$ brew install pyenv pyenv-virtualenv pyenv-which-ext

Anyways here’s the shell initialization code.

  if which pyenv > /dev/null; then eval "$(pyenv init -)"; fi

Enable plenv

Plenv for managing Perl installations.

export PLENV_HOME="$HOME/.plenv"
if [ -d "$PLENV_HOME" ] ; then addpath "$PLENV_HOME/bin"; fi
if which plenv > /dev/null; then eval "$(plenv init -)"; fi

Enable rakubrew

Rakubrew manages installed versions of Raku. I generally find its versions fresher than what apt offers and its ability to hand me a functioning build more consistent than Homebrew.

  export RAKUBREW_HOME="$HOME/.rakubrew"
  if [ -d "$RAKUBREW_HOME" ]; then addpath "$RAKUBREW_HOME/bin"; fi
  if which rakubrew > /dev/null; then eval "$(rakubrew init Zsh)"; fi

  if [ -d "$HOME/.perl6/bin" ] ; then
      addpath "$HOME/.perl6/bin"

Enable deno dev

Deno sure looks interesting.

  export DENO_INSTALL="$HOME/.deno"
  if [ -d "$DENO_INSTALL" ] ; then addpath "$DENO_INSTALL/bin"; fi

Enable rust dev

if [ -f "$HOME/.cargo/bin" ] ; then addpath "$HOME/.cargo/bin"; fi

Load aliases

Should aliases get loaded for all sessions, or only the interactive ones? No idea yet. I’ll put it here for now.

  source "$HOME/.aliases"

Configure nnn

nnn is a terminal-based file browser that I forgot all about until I was cleaning up this config.

export NNN_FALLBACK_OPENER=xdg-open

Configure bat

bat is my preferred terminal file viewer. Syntax highlighting, line numbers, git info in the gutter.

if which bat &> /dev/null; then
   # TODO: Set up dotfiles/config/bat/config
   export BAT_THEME="TwoDark"
   export PAGER='bat'
   export MANPAGER="sh -c 'col -bx | bat -l man -p'"

Late local shell initialization

Again, environment-specific stuff. Assumes everything else has been defined. A good spot for starting services.

if [ -f "$HOME/.zshenv_local_after" ]; then
    source "$HOME/.zshenv_local_after"


Loaded for interactive sessions.


Set umask

umask 002

Antigen bundles

Whole lot of debate out there on the right way to extend the shell. I arbitrarily chose Antigen, but still lean on Oh My Zsh. I don’t know enough Zsh to really care about the right approach to plugins.

  source ~/antigen.zsh

  antigen use oh-my-zsh

  antigen bundle git
  antigen bundle rbenv
  antigen bundle taskwarrior
  antigen bundle tmux

  # Bundles found in the wild.
  # https://github.com/lukechilds/zsh-nvm
  export NVM_COMPLETION=true
  antigen bundle lukechilds/zsh-nvm

  # aesthetic bundles and theme
  antigen bundle zsh-users/zsh-syntax-highlighting
  antigen theme gozilla

  antigen apply

  # The antigen plugin seems to miss PATH_add?
  eval "$(direnv hook zsh)"

WSL/specific stuff

I like to have an Xorg layer for my WSL experience.

  if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null; then
      export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0.0

Configure history

Manage history. See http://zsh.sourceforge.net/Guide/zshguide02.html#l17

setopt share_history

Configure broot

broot is another console-based file explorer. And like nnn, I don’t spend nearly enough time figuring out how to use it.


if [ -f "$BROOT_LAUNCHER" ]; then
    source "$BROOT_LAUNCHER"

Start keychain

Keychain manages SSH & GPG keys for shell sessions, so you only need to enter your managed keys once per session. Handy in both awesomewm and WSL.

if which keychain > /dev/null; then eval `keychain --eval --agents ssh id_rsa randomgeek_rsa`; fi


Originally just .bash_aliases, but I figure I can use the same file for Zsh. Right? God I hope so.

  alias ackpy='ack --type=python'
  alias bbd='brew bundle dump --force --describe --global'
  alias be='bundle exec'
  alias dnuke='docker kill $(docker ps -q);docker system prune --all --volumes -f'
  alias e='emacs -nw'
  alias kexp='mplayer http://live-aacplus-64.kexp.org/kexp64.aac'
  alias ls='ls -hF'
  alias ll='ls -lhF'
  alias l='ls -lAhF'
  alias pr='poetry run'
  alias pri='poetry run invoke'
  alias ta='task add'
  alias tad='task add due:eod'
  alias taw='task add due:eow'
  alias tam='task add due:eom'
  alias tn='task-note'
  alias tickets='task +ticket'
  alias unflicker='xrandr --output DisplayPort-2 --mode 2560x1440 --rate 59.95'
  alias work='task +work'
  alias ymd='date +"%Y%m%d"'
Got a comment? A question? More of a comment than a question? Talk to me about this config!

Back to the config section