I’ve finally made the switch from ZSH to the fish shell. Unlike ZSH, I don’t need to install and manage a whole host of plugins. Everything I want comes packaged right in as part of the shell itself.

The Switch

In this post, I describe how to switch to the fish shell and some of the interesting features it offers.

Install

Installing fish is a piece of cake. On Ubuntu, a fish package is readily available. An additional PPA supplies newer versions of fish than those that ship with the distribution. The following steps install fish through the PPA.

  1. Add the fish PPA.

    sudo apt-add-repository ppa:fish-shell/release-3
  2. Update Aptitude with the new PPA.

    sudo apt update
  3. Install the fish package.

    sudo apt -y install fish

Switch

Once installed, switching is just a matter of updating the login shell.[1]

  1. Register /usr/bin/fish as a login shell by adding its path to /etc/shells.

    Debian provides a convenient add-shell command to accomplish this.

    sudo add-shell /usr/bin/fish
  2. Set the current user’s login shell to fish with chsh.

    chsh -s /usr/bin/fish
    Password:
  3. Now, open up a fresh terminal session to start using fish.

    You should see a fresh, vanilla shell prompt like the following.

    Welcome to fish, the friendly interactive shell
    Type help for instructions on how to use fish
    jordan@jwillkers ~>

Configure

Adjusting basic configuration options is quick ang easy with fish.

  1. Configuring the fish shell is as easy as selecting options the provided by the web interface.

    fish_config
  2. Style the prompt.

    If you like that oh-my-zsh feel as I do, you might want to use robbyrussell’s theme for your shell prompt. If you like doing things the old fashion way - or want to set the prompt remotely, here’s a quick rundown.

    1. Make sure the ~/.config/fish/functions directory exists.

      mkdir -p ~/.config/fish/functions
    2. Then copy over the desired prompt configuration, robbyrussell.fish in this case, from /usr/share/fish/tools/web_config/sample_prompts/.

      cp /usr/share/fish/tools/web_config/sample_prompts/robbyrussell.fish ~/.config/fish/functions/fish_prompt.fish
  3. Adjust the color theme.

    The color theme can be important when your terminal’s color theme obscures autosuggestions. Solarized is my color scheme of choice. The fish_config web tool outputs the commands that set the various variables for a color theme when one is selected. This is easy to translate to a command-line which can be quite helpful when configuring remotely. As an example, the following command sets the color scheme to solarized dark.

    set -U fish_color_normal normal; \
      set -U fish_color_command 93a1a1; \
      set -U fish_color_quote 657b83; \
      set -U fish_color_redirection 6c71c4; \
      set -U fish_color_error dc322f; \
      set -U fish_color_end 268bd2; \
      set -U fish_color_selection white --bold --background=brblack; \
      set -U fish_color_search_match bryellow --background=black; \
      set -U fish_color_history_current --bold; \
      set -U fish_color_operator 00a6b2; \
      set -U fish_color_param 839496; \
      set -U fish_color_cwd green; \
      set -U fish_color_cwd_root red; \
      set -U fish_color_valid_path --underline; \
      set -U fish_color_autosuggestion 586e75; \
      set -U fish_color_user brgreen; \
      set -U fish_color_escape 00a6b2; \
      set -U fish_color_cancel -r; \
      set -U fish_pager_color_completion B3A06D; \
      set -U fish_pager_color_description B3A06D; \
      set -U fish_pager_color_prefix cyan --underline; \
      set -U fish_pager_color_progress brwhite --background=cyan; \
      set -U fish_color_host normal; \
      set -U fish_color_match --background=brblue; \
      set -U fish_color_comment 586e75
  4. Customize the greeting message with the variable fish_greeting.[2]

    set -U fish_greeting ""
  5. Enable backwards-incompatible features.[3]

    set -U fish_features stderr-nocaret qmark-noglob
  6. Enable Vi mode.

    For user’s who prefer Vi and Vim, there is a nifty method of enabling Vim keyboard shortcuts in addition to the default Emacs shortcuts. Creating the file below will enable this and default to insert mode.

    ~/.config/fish/functions/fish_user_key_bindings.fish
    function fish_user_key_bindings
        fish_default_key_bindings -M insert
        fish_vi_key_bindings insert
    end

    Override the fish_mode_prompt function with an empty function to disable the Vi mode indicator.

    ~/.config/fish/functions/fish_mode_prompt.fish
    function fish_mode_prompt
    end
  7. Now, if you’re thinking about .fishrc, forget about it.[4] The equivalent of ~/.bashrc and ~/.zshrc is ~/.config/fish/config.fish but a lot of configuration is better achieved - and organized - through universal variables and autoloading functions.

  8. Update the PATH environment variable in fish to suit your needs.[5]

    Version 3.2.0 of fish introduced the command fish_add_path which makes permanently adding a path to PATH super easy. Just use use the command followed by the path to add, and that’s it! No fiddling with configuration files necessary. It takes care of duplicates for you, too. Some Linux distributions don’t include /usr/local/bin in PATH by default. Adding /usr/local/bin to the end of the list in PATH is as simple as this.

    fish_add_path /usr/local/bin

    The -p flag prepends the given path to PATH. To place /usr/local/bin at the beginning of PATH, use this command.

    fish_add_path -p /usr/local/bin

    When messing with PATH prior to version 3.2.0, use fish’s dedicated internal variable fish_user_paths. This variable is special and populates PATH. With the set command, prepend /usr/local/bin to PATH as follows.

    set -pU fish_user_paths /usr/local/bin

    The -p option prepends a value to the given variable. The -U option signifies a universal variable, which persists the variable in the future and across any currently running fish sessions. This reduces the overhead of having to mess about with shell startup files. These variables can be managed in the file ~/.config/fish/fish_variables.

Learn

A lot of fish functionality has been covered already, but "there’s always more to learn" as they say. A few additional fish topics are covered here.

  • Export shell variables with the -x flag.

    This makes the variables accessible to other programs. PATH is automatically exported from the contents of the un-exported fish_user_paths variable, making this an exception. For everything else, export the variable by calling set with the -x flag. To add a value to the beginning of the LD_LIBRARY_PATH environment variable and export it, use set as follows.

    set -px LD_LIBRARY_PATH /usr/local/lib
  • Understand how fish handles PATH variables.

    The fish shell stores lists internally as arrays of strings. This is fundamentally different from how shells typically represent many fundamental variables which contain lists of paths, such as PATH and LD_LIBRARY_PATH. Classic shells store these "lists" as a single string of colon-separated paths. Many applications and programs expect the incumbent formatting, so fish treats these as special variables called PATH variables. When printing or joining PATH variables, colons are used to delimit values when the variables are quoted. Otherwise, spaces separate each path in the list. Any variable ending in PATH is automatically treated as a PATH variable. So, when using set to deal a PATH variable, you can still treat it as you would any other list in fish.[6] As an example, the following command adds /usr/local/lib to the beginning of the classic LD_LIBRARY_PATH variable and appends ~/lib to the end.

    set -x LD_LIBRARY_PATH /usr/local/lib $LD_LIBRARY_PATH ~/lib

    When quoting the variable, it must be modified like so to achieve the same result.

    set -x LD_LIBRARY_PATH /usr/local/lib:"$LD_LIBRARY_PATH":~/lib
  • Take advantage of autosuggestions.

    Just start typing a command and fish will provide suggestions on your prompt. As autosuggestion appear from your history, choose the suggested line with . To select only the next word of the suggestion, use Alt+.

  • Use fish’s searchable history instead of Bash’s reverse-search-history.

    This one has required a bit of learning curve since I’m so used to finding previous commands by searching with Ctrl+R. With fish, this is even simpler. Start typing the letters, word, or phrase you want to match. Then, just press Alt+ to scroll backwards through history for matches.

  • Another nifty feature is the recursive wildcard which automatically descends into subdirectories for matching a particular pattern.

    The following example recursively searches and lists all files ending in .fish in ~/.config.

    ls ~/.config/**.fish
    /home/jordan/.config/fish/functions/fish_prompt.fish

Troubleshoot

Common issues involve various environment variables and initialization functionality which assumes the default login shell is Bash. Some people place fish in their ~/.bashrc file to start fish from within Bash so that all environment variables are correctly configured. I prefer to use fish as my login shell and file bugs as necessary for projects to properly support it, but this is definitely a bit a of a pain but thus is progress, eh?

Conclusion

There’s a lot to learn about the fish shell. It provides an extremely convenient feature set and is attempting to solve issues inherited from shells of generations past. I love it, and its a great improvement to my workflow.