Switching from zsh to fish

a new fish

tl;dr How easy is it to switch from zsh to fish ? Is it worth it ?


In the beginning … there was bash and all was good.

The built-in OSX terminal catered to my needs whenever I had to wander into shell land.

Mac OSX Terminal app

The Road to Enlightenment

But when I started hearing about iTerm2, themes (Solarized, Tomorrow, etc.) and dotfiles (Github dotfiles, Holman dotfiles, etc.), there was only one possible outcome: zsh.

And I embraced oh-my-zsh.

The almost infinite amount of themes, functionality I never knew I needed until I used it (z, history substring search, syntax highlighting, etc.) made me feel at home.

Bash was just a kindergarten kid compared to my new and all-mighty shell.

I heard some rumors about this shell with a funny name, took a look at the website and said, hey … zsh does all that already ! You shall not pass !

Make things as simple as possible, but not simpler

The thing is … I strive to simplify my work environment as much as I can.

As great as oh-my-zsh is, it brings a lot of baggage. If you want to customize, you need to dig deeper and dedicate a fair amount of time to make it work exactly the way you want.

When I double checked the fish documentation, it promised a lot of built-in functionality with minimal fuss if you wanted to extend and customize.

Was that really the case ?

Only one way to find out.

The Red pill

I took the easy route

$ brew install fish

(you are using Homebrew to install anything on your Mac right ?)

Right out of the box, you get syntax highlighting, command muted suggestion, history substring search


.zshrc is nowhere to be found, instead you have .config/fish/config.sh, which is very similar, only the syntax changes.

This is my config.fish

set -x PATH /usr/local/opt/coreutils/libexec/gnubin $HOME/bin /usr/local/bin /usr/bin /bin /usr/sbin /sbin

set -x GOROOT /usr/local/opt/go/libexec
set -x GOPATH ~/code

# fuxor git to non-interactively merge commits


# Set where to install casks

set -x HOMEBREW_CASK_OPTS "--appdir=/Applications"

# Setup terminal, and turn on colors

set -x TERM xterm-256color
set -xU LS_COLORS "di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34:su=0:sg=0:tw=0:ow=0:"

# Enable color in grep

set -x GREP_OPTIONS '--color=auto'
set -x GREP_COLOR '3;33'

set -x LESS '--ignore-case --raw-control-chars'
set -x PAGER 'less'
set -x EDITOR 'nano'

set -x LANG en_US.UTF-8
set -x LC_CTYPE "en_US.UTF-8"
set -x LC_MESSAGES "en_US.UTF-8"

source functions/z.fish

Two notes:

  • I always
    $ brew install coreutils

and put the GNU versions of the core commands first in the path. That’s why I set the LS_COLORS environment variable, rather than the LSCOLORS that you would normally use in OSX.

  • I currently hold some miscellaneous scripts in ~/bin, which I then put in my path for quick access. In the future, I will symlink those scripts to /usr/local/bin, so I won’t need to add $HOME/bin in my PATH env var.

Extension and customization is achieved via a common path: functions and more elegantly, autoloading functions.

So, aliases are no more, instead you declare a function inside self contained file in ~/.config/fish/functions.

Very simple and very elegant.

These are the functions I currently have:


  • fish_prompt.fish | customizes my prompt
  • gpl.fish | prints a pretty git log
  • hal.fish | ssh’s to one of my servers
  • l.fish | is a shortcut to ls -al —color=always
  • mkd.fish | creates a dir and cd’s to it
  • skynet | ssh’s to one of my servers
  • wopr | ssh’s to one of my servers
  • z.fish | is fish’s version of z (you can find it here)

This is my prompt (a slightly modified clearance theme that you can find here)

# name: clearance

# ---------------

# Based on idan. Display the following bits on the left:

# - Virtualenv name (if applicable, see https://github.com/adambrenecki/virtualfish)

# - Current directory name

# - Git branch and dirty state (if inside a git repo)

function \_git_branch_name
echo (command git symbolic-ref HEAD ^/dev/null | sed -e 's|^refs/heads/||')

function \_git_is_dirty
echo (command git status -s --ignore-submodules=dirty ^/dev/null)

function \_remote_hostname
echo (whoami)@(hostname)

function fish_prompt
set -l cyan (set_color cyan)
set -l yellow (set_color yellow)
set -l red (set_color red)
set -l blue (set_color blue)
set -l green (set_color green)
set -l normal (set_color normal)
set -l mywhite (set_color -o white)
set -l mygreen (set_color -o green)

set -l cwd $blue(pwd | sed "s:^$HOME:~:")

set -l dove $mygreen (pwd | sed "s:^$HOME:~:")

set -l whowheredate '[' $mywhite (_remote_hostname) $normal ' ' (date "+%H:%M") '] '

# Output the prompt, left to right

# Add a newline before new prompts

echo -e ''

# User@server time

echo -n -s $whowheredate

# Print pwd or full path

echo -n -s $dove $normal

# Show git branch and status

if [ (_git_branch_name) ]
set -l git_branch (\_git_branch_name)

    if [ (_git_is_dirty) ]
      set git_info '(' $yellow $git_branch " ±" $normal ')'
      set git_info '(' $green $git_branch $normal ')'
    echo -n -s ' ' $git_info $normal


# Terminate with a nice prompt char

echo -e ''
echo -e -n -s '⟩ ' $normal

This is gpl.fish

function gpl
git log --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit


function l
ls -lah --color=always $argv


I’m currently using fish as my shell and it has been working great. I definitely recommend it.

I’ve found only one missing functionality: history sharing between sessions.

I generally have a couple tabs open, and fish doesn’t share commands between tabs.

I understand it’s been worked on.

Image Source wembley

Sharing is caring!

If you got value from this article, please consider sharing it with your friends and colleagues.