Zsh was my default shell for many years. I began with Linux and used Bash as my first shell. After trying various Linux distributions (Ubuntu, Cinnamon, Manjaro, Arch), I found myself frequently modifying and fixing my .bashrc. When I switched to a MacBook Pro, I adopted Zsh, enhanced by Oh-My-Zsh, as my default shell for a long time.

A few weeks ago, I discovered a new shell named fish. It’s fast, smart, and offers impressive built-in plugins and themes. I decided to make Fish my default shell and explore its features.


Fish Shell 🐠

Fish (short for friendly interactive shell, stylized in lowercase) is a Unix-like shell focused on interactivity and usability. Unlike other shells, Fish is feature-rich by default rather than highly configurable. It’s considered an exotic shell because it doesn’t adhere to POSIX shell standards by design.

Why Switch?

  1. Startup Delays: Over time, I noticed minor delays when starting Zsh on macOS. While small, these delays became more noticeable. Fast systems give better feedback, as explained by the Doherty Threshold principle.
  2. Pre-Built Features: My .zshrc included various plugins and configurations provided by oh-my-zsh. Fish offers most of these features out of the box.

With this in mind, I aimed to replicate my Zsh setup in Fish efficiently.


Getting Started

Installing Fish

On macOS, install Fish using Homebrew:

$ brew install fish
$ fish --version
fish, version 3.6.1

Setting Fish as the Default Shell

Add Fish to the list of available shells and set it as the default shell:

$ echo /opt/homebrew/bin/fish | sudo tee -a /etc/shells
$ chsh -s /opt/homebrew/bin/fish

Recognizing Homebrew Binaries

Ensure Fish recognizes Homebrew binaries by adding them to Fish’s path:

$ fish_add_path "/opt/homebrew/bin/"
$ which brew
/opt/homebrew/bin/brew

Updating Completions

Generate completion files to enable better command suggestions:

$ fish_update_completions

Installing Oh-My-Fish

Like Oh-My-Zsh, Fish can be enhanced using Oh-My-Fish. Install it with:

$ curl https://raw.githubusercontent.com/oh-my-fish/oh-my-fish/master/bin/install | fish

Using Fisher for Plugin Management

Fisher is a powerful plugin manager for Fish:

$ brew install fisher

Basics

The Fish configuration file, equivalent to .bashrc or .zshrc, is located at ~/.config/fish/config.fish. Fish uses a unique scripting language with syntax that differs slightly from Bash. For instance:

  • Blocks like if, while, and for are enclosed with end.
  • Functions are defined with the function keyword and saved in .fish files.

Abbreviations, Aliases, and Functions

  • Abbreviations: Short text expands into longer commands after pressing space or enter.
  • Aliases: Defined in ~/.config/fish/config.fish. In Fish, alias is a wrapper for function.
  • Functions: Saved under ~/.config/fish/functions.

Example:

$ alias lsa="ls -la"

This creates a Fish function with the description alias lsa=ls -la. You can view it by pressing the tab key for completions.

To migrate aliases from .zshrc, paste them into the terminal and use funcsave to save them as functions:

$ funcsave lsa

Environment Variables

Use Universal Variables to set environment variables:

$ set -Ux EDITOR "vim"

This writes the variable to ~/.config/fish/fish_variables and exports it to all active shells.


Plugins

Use Fisher and Oh-My-Fish to install helpful plugins:

$ fisher install patrickf1/fzf.fish
$ fisher install edc/bash
$ fisher install laughedelic/pisces

Additionally, install plugins like:

$ omf install z extract kill-on-port nvm android-sdk

Prompts

I use Starship, a cross-shell prompt manager. To enable it in Fish:

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

Another great option is Tide. Install it with Fisher:

$ fisher install IlanCosman/tide@v6
$ tide configure

Benefits

Switching to Fish made my shell configuration more organized and faster. Fish scripts are more readable compared to Zsh or Bash. Here are some features I love:

  • Command autosuggestions with fish_update_completions.
  • Fuzzy search for history (CTRL + R) using fzf.
  • Fast, pre-built plugins.

Challenges

While the migration was quick, I faced a few challenges:

  • Some Zsh functions required rewriting due to syntax differences.
  • Converting Zsh history using zsh-history-to-fish worked, but timestamps and multiline commands needed manual fixes.
  • Scripts exporting ENV variables (export VAR=value) caused errors in Fish.

Conclusion

Migrating from Zsh to Fish took less than 10 minutes, excluding minor adjustments. Fish feels faster, cleaner, and more intuitive. If you’re curious, give it a try! You can always revert to your previous setup if it doesn’t work for you.