DEV Community

Cover image for A modern, ergonomic Unix shell configuration with Fish
Jonathan E. Magen
Jonathan E. Magen

Posted on

A modern, ergonomic Unix shell configuration with Fish

Like many others, I spend enough time in the shell that even small improvements can yield huge productivity gains. This post is an attempt to catalog several of quality-of-life enhancements I've made over recent months to my shell setup.


Like most folks I know, I began my command-line life with good 'ol Bash and actually still do most of my shell scripting in Bash. Even if I eventually rewrite the command in another language, many of my tools begin life as a Bash script.

At some point, perhaps ~10 years ago, I was introduced to ZSH with the venerable Oh My ZSH framework. For me, this was a huge awakening because it taught me about how much shell improvements can impact your life. I gradually evolved my ZSH setup to use package managers like antigen and began to invest more in creature comforts.

Since that time, while the productivity gains have compounded, friends recommended Fish to me, the Friendly Interactive SHell. For my workflows this was the major boost which catapulted my shell configuration from "good enough" to actually "working well".

Why Fish?

The first thing I noticed was that the default configuration of Fish was equivalent to about 30-45 lines of ZSH config. So even starting fresh with Fish was already roughly on-par with my previous ZSH configuration. Moreover, I find that the modern Fish dialect, especially its error-case semantics, are vastly superior considering the frequency with which things go wrong. In this way, Fish gives me a much more solid foundation on which to build a proper environment for myself.

Diving into my Fish config

The core of any Fish config is the file found at ~/.config/fish/ on most systems.

Mine looks something like this:

#!/usr/bin/env fish

# Configure my shell's prompt
starship init fish | source

# Make it easy to switch to commonly-used directories
zoxide init fish | source

# Set up my ruby environment with rbenv
status --is-interactive; and source (rbenv init -|psub)
set -gx PATH "$HOME/.rbenv/bin" $PATH

# Use VS Code as the default editor and block until the file is closed
set -x EDITOR 'code --wait'

# Make skim do the right thing by default
set -x SKIM_DEFAULT_COMMAND "fd --type f"

## Elixir and Erlang
# Elixir package timeout to prevent stalled downloads
# Elixir IEx history config
set -x ERL_AFLAGS '-kernel shell_history enabled'

## Rust
# Enable a shared Cargo target directory to cut down on disk usage
set -x CARGO_TARGET_DIR $HOME/.cargo/shared_target
# Ensure that Cargo binaries are in the PATH
set PATH $HOME/.cargo/bin/ $PATH

# Helpful expansions
abbr -a -g gp git push
abbr -a -g gc git commit -asm
abbr -a -g gst git status

# Helpful aliases
command -q exa; and alias la="exa -abghl --git --color=automatic"
command -q exa; and alias ll="exa -bghl --git --color=automatic"

# Fisher!
if not functions -q fisher
    set -q XDG_CONFIG_HOME; or set XDG_CONFIG_HOME ~/.config
    curl --create-dirs -sLo $XDG_CONFIG_HOME/fish/functions/
    fish -c fisher

## Node
# Volta config
set -gx VOLTA_HOME "$HOME/.volta"
set -gx PATH "$VOLTA_HOME/bin" $PATH
Enter fullscreen mode Exit fullscreen mode


In order of appearance, here are some of the tools which feature in the above Fish config:

  • The Starship prompt is a terrific alternative to slower prompts written in pure shell. It supports a whole variety of status features, is quite performant, and has easy-to-understand configuration options.
  • I use zoxide to quickly switch to recently used directories, or to paths which I use particularly commonly. It can be hard to remember to use z instead of cd but once you get the hang of it you'll never go back.
  • My setup relies heavily on rbenv since I use Ruby for automation all over the place.
  • Tools like skim work great with others like fd to provide additional functionality when it comes to locating files and filtering through search results. I also use skim's sk command pretty heavily in other scripts when interactivity is required.
  • Even though ls is a good-old standby, I prefer the aliases for ll and la that rely on exa. These aliases default to showing important information like git status and can even prevent me from needing to launch subsequent, exploratory commands.
  • I really fancy the Fisher package manager for installing Fish libraries, tools, and commands!
  • Since I'm a big fan of toolchains as code, I've fallen in love with volta for managing my Node.js and Yarn versions. Don't forget commands like volta pin in Node.js projects to lock in versions to your package.json file!


So I guess you could say that Fish really hooked me. Get it? Anyway, thanks for reading this post and feel free to reach out to me with your favorite shell tricks and quality-of-life improvements! I really love this stuff and would love to hear from you.

Top comments (0)