ZSH was my default shell for many years. I have modified and moved .zshrc across many different distributions of Linux. Recently I started working on a MacBook Pro, which has ZSH as the default shell. Using it for few months now, I noticed a few things about my ZSH configuration:

  1. The startup of the new shell had minor delays before it could be used. These delays weren’t huge, but I started noticing it more and more on MacOS. We tend to interact faster with systems that gives us faster feedbacks.
  2. My .zshrc configuration had some plugins and configurations from oh-my-zsh that Fish shell mostly provides out-of-the-box.

With this in mind, I wanted to know how fast can I migrate to Fish shell while converting my current .zshrc.

Fish Shell 🐠

Getting ready

My .zshrc configuration looks like this:

# ---- ZSH ---- #
export ADOTDIR="$HOME/.antigen"
# Source antigen zsh plugin manager
source $HOME/.antigenrc
antigen init
# Autosuggestion color highlight
export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#F2F0F6,underline"
export ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=30
# History settings.
export HISTSIZE=1000000
export HISTCONTROL='ignoreboth'
export HISTORY_IGNORE="(clear|history)"
SAVEHIST=$HISTSIZE
# Add wisely, as too many plugins slow down shell startup.
plugins=(copyfile
         extract
         git-extras 
         git-flow-avh 
         zsh-autosuggestions 
         zsh-completions
         zsh-syntax-highlighting 
         colored-man-pages
         z)

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

# ---- User Configuration ---- #
## Nix specific
export NIX_PATH=darwin-config=$HOME/.nixpkgs/darwin-configuration.nix:$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH
source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh

eval "$(starship init zsh)"
# Leave less preview on screen when quit
export LESS="-RX"
export EDITOR='nvim'
export LESS_TERMCAP_md="${yellow}"
# ---------------------------- #


# ---- Alias ---- #
alias vim="nvim"
alias l="exa --long --header --all"
alias ls="exa --long --header"
alias tree="exa --tree --level=2"
alias cat="bat"
alias ping="ping -c 3"
# Add verbose output on these commands
alias rm="rm -v"
alias mkdir="mkdir -v"
alias mv="mv -v"
alias cp="cp -v"
# --------------- #

#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

Setup

Install Fish and Fisher (plugin manager for Fish):

brew install fish fisher

Fish configurations are located at ~/.config/fish. Now we can activate fish shell using fish command to get started.

Alias

To convert aliases, simply paste them once in terminal and use funcsave to save them at ~/.config/fish/functions:

alias cp="cp -v"
alias mv="mv -v"
...
funcsave cp mv ...

ENV variables

Use Universal Variables as:

set -Ux EDITOR "nvim"
...

This will write to ~/.config/fish/fish_variables once and export variables to all active shells as well.

Plugins

Use Fisher to install several helpful plugins.:

fisher install jethrokuan/z             # Jump around the folders
fisher install decors/fish-colored-man
fisher install oh-my-fish/plugin-peco    # Ctrl + R for searching history
fisher install oh-my-fish/plugin-extract

Note that some of Fish plugins are enabled by default and there is no need to set the up like in ZSH.

I also found below plugins useful while converting my .zshrc (though you may won’t need them)

fisher install reitzig/sdkman-for-fish
fisher install lilyball/nix-env.fish
fisher install jorgebucaran/nvm.fish

Prompt

I use Starship which is “Cross-Shell Prompt” so my configurations for Starship are automatically applied. To enable it, just use:

echo "starship init fish | source" >> $HOME/.config/fish/config.fish

Results

Benchmarks

Using hyperfine at first run, I’ve noticed Fish is faster than ZSH to some extent:

Benchmark results using Hyperfine on both ZSH and Fish

I got curious why both shells take so much time to start. Profiling both Fish and ZSH, I figured out that both of them have performance degradation due to SDKMan enabled (manages multiple Java Distributions similar to NVM).

Once SDKMan is removed for both shells, I re-ran benchmarks again:

Benchmark results using Hyperfine on both ZSH and Fish

With both shells being almost similar in my setup, Fish shell ran 3 times faster than ZSH.

Benefits

I find my shell configuration more organized now using Fish. Syntax of Fish scripts feel lot more readable compared to ZSH/Bash scripts. Other noticeable features that I found useful:

  • Auto suggest command completions (fish_update_completions)
  • CTRL + R with Peco for searching history easily
  • Great plugins
  • Faster compared to my ZSH

Gotchas

Although converting configurations was extremly fast, I spent a lot of time on following:

  • I keep backups of ZSH history dating back several years. There is a tool to convert ZSH history to Fish, but it didn’t kept timestamp of execution. Also, some of multiline commands (such as curl) broke fish_history file. The tool works, but may have to manually correct errors.
  • Poetry completions produce errors. I though it was error something due to Fish, but found out later that this will be fixed.
  • Some of the builds scripts (Makefiles, builds tasks…) exported certain ENV variables using export so running it with Fish produced errors.

Conclusion

In this state, I have migrated from ZSH to Fish really fast and set it as default shell, keeping the same configurations. Without gotchas that I ran in to, time of converting was less than 10 minutes. I find my shell much faster now, using new plugins and some other that Fish provides by default.

At the end, I suggest you try it. If it doesn’t work, you can always go back to your previous configuration.


Shared on /r/fishshell