My ultimate shell setup with Fish shell and Tmux (Part 1)

I’ve recently been asked to share my shell setup, which if you ask me, that’s one of the highest levels of praise you can achieve as a software developer 🤣 In this article, I’ll describe the setup I’ve been using for the past few years, and how to achieve similar results yourself.

🖼️ The result

First, let’s look at what we’ll try to achieve by looking at a screenshot I took last year when showing off to a senior co-worker of mine. Visually speaking, almost nothing changed since then, so let me just be lazy for a while and re-use this overly annotated screenshot.

I think some people go for very minimalist shell setups, and I suppose you could say that I prefer the exact opposite of minimalist in this regard 😅 The screenshot above is missing two significant elements, which would be the exit code of previously executed command and presence of background processes - not to worry, these elements are not omitted, just hidden in case no interesting information is available to show (last process terminated successfully/no background processes are running respectively).

👆 Prerequisites

To achieve similar results, I believe you’ll need two pieces of puzzle that I’ll not cover in detail in this article:

  • Capable terminal emulator (macOS’ Terminal.app doesn’t qualify) – I’ve been living in the iTerm2 land since I migrated to macOS, always configured the superior Solarized Dark color theme;
  • Nerd-patched font for all the special glyphs that our shell will show us – either patch a font of your liking yourself or go with one of the pre-patched fonts; popular choices include FiraCode, Hack, Inconsolata, Iosevka and others.

🐟🐚 It all starts in the shell…

After using bash for many years and zsh for many more years, I switched to fish shell a few years ago. While zsh is a fine shell, I felt that fish can be more user-friendly and easier to customize – but I recognize that this doesn’t come without drawbacks, namely that fish shell doesn’t respect the traditional POSIX syntax so you’ll have to definitely alter some of your habits. Let’s first install fish shell and then I’ll share a few tricks to get more familiar with fish shell.

brew install fish
echo /usr/local/bin/fish | sudo tee -a /etc/shells
chsh -s /usr/local/bin/fish $(whoami)

Say “bye-bye” to bash/zsh because the next time you open a terminal, you should be welcomed by a friendly interactive shell, like so:

Next, install Oh My Fish, which is essentially a package manager for Fish shell, along with some essential plugins and themes:

curl -L https://get.oh-my.fish | fish
omf install bobthefish # Theme
omf install aws       # AWS integration & command completion
omf install bass      # Allows running heavily Bash-dependant utilities, like nvm
omf install brew      # Integrate Homebrew paths
omf install colored-man-pages
omf install export    # Bring back Bash-like export command
omf install https://github.com/jhillyerd/plugin-git
omf install osx       # Finder/macOS integration and utilities
omf install wifi-password
omf install z         # Autojump implementation

For more information about what these do, check the package list – for example, wifi-password shows you the current WiFi password – not something you need every day, but when you need it, it’s very nice to have; z allows you to quickly jump across often visited directories just by typing part of their names; and the git plugin (documentation available here) gives you more git aliases and abbreviations than you could ever remember.

You’ll notice that even with the bobthefish theme installed and automatically activated, your shell prompt still looks way different from the first screenshot I presented – so let’s configure the shell and prompt. Listed below is the content of the ~/.config/fish/config.fish file I currently use, but feel free to adjust it to your heart’s content:

set -gx COLORTERM truecolor
set -gx EDITOR nvim
set -gx LANG cs_CZ.UTF-8    # Adjust this to your language!
set -gx LC_ALL cs_CZ.UTF-8  # Adjust this to your locale!
set -gx VIRTUAL_ENV_DISABLE_PROMPT true
set -gx GOPATH $HOME/go
set -x PATH $GOPATH/bin $HOME/.composer/vendor/bin $HOME/Library/Python/3.7/bin $PATH
set -gx HOMEBREW_AUTO_UPDATE_SECS 86400
set -gx DOCKER_BUILDKIT 1
set -gx COMPOSE_DOCKER_CLI_BUILD 1
set -g fish_key_bindings fish_vi_key_bindings
set -g fish_bind_mode insert

# Title options
set -g theme_title_display_process yes
set -g theme_title_display_path yes
set -g theme_title_display_user yes
set -g theme_title_use_abbreviated_path yes

# Prompt options
set -g theme_display_ruby yes
set -g theme_display_virtualenv yes
set -g theme_display_vagrant no
set -g theme_display_vi yes
set -g theme_display_k8s_context no # yes
set -g theme_display_user yes
set -g theme_display_hostname yes
set -g theme_show_exit_status yes
set -g theme_git_worktree_support no
set -g theme_display_git yes
set -g theme_display_git_dirty yes
set -g theme_display_git_untracked yes
set -g theme_display_git_ahead_verbose yes
set -g theme_display_git_dirty_verbose yes
set -g theme_display_git_master_branch yes
set -g theme_display_date yes
set -g theme_display_cmd_duration yes
set -g theme_powerline_fonts yes
set -g theme_nerd_fonts yes
set -g theme_color_scheme solarized-dark

bind -M insert \cg forget

if which asdf > /dev/null; status --is-interactive; and source (brew --prefix asdf)/asdf.fish; end
if which direnv > /dev/null; direnv hook fish | source; end
if which goenv > /dev/null; status --is-interactive; and source (goenv init -|psub); end
if which rbenv > /dev/null; status --is-interactive; and source (rbenv init -|psub); end
if which swiftenv > /dev/null; status --is-interactive; and source (swiftenv init -|psub); end

Line 42 binds a forget function to the key combination of Ctrl-G, so let’s create the forget function right now by pasting the following into ~/.config/fish/functions/forget.fish:

function forget
    set -l cmd (commandline | string collect)
    printf "\nDo you want to forget '%s'? [Y/n]\n" $cmd
    switch (read | tr A-Z a-z)
        case n no
            commandline -f repaint
            return
        case y yes ''
            history delete --exact --case-sensitive -- $cmd
            commandline ""
            commandline -f repaint
    end
end

What this does is allows you to easily remove command from shell’s history, for example if you typed sensitive information or made a typo in a command that you don’t want to get reminded of in the future. Activate it by going up the history with the up arrow key ⬆️⌨️ until you hit the command you wish to forget, then activate the function by pressing Ctrl-G. Speaking of fish functions, here’s one more you might find useful – get a random Git-related tip as spoken with a random pony anytime you open a new shell! First, let’s install the dependencies:

brew install ponysay
npm install -g git-tip

Then put the following text into ~/.config/fish/functions/fish_greeting.fish:

function fish_greeting
  git-tip | ponysay
end

If you followed the guide properly (and if I haven’t missed anything), your terminal should now look fairly similar to mine!

🦺 Fish survival guide

As I mentioned at the beginning of the article, Fish is often different. I hope that this little table will help you overcome the most problems you could face:

bash command fish equivalent Notes
true && true true; and true
true || false true; or false
echo "Date: $(date)" echo "Date: "(date)
export A=b set -gx A b You can keep using export with the OMF plugin we installed
echo "A=${A}" echo "A="$A
for i in $(seq 10); echo $i; done for i in (seq 10); echo $i; end
Ctrl-R for history search Type search term, then press ⬆️ key Or omit search term for linear history browsing

For more in-depth survival guide, be sure to check the official documentation.

And since this article is getting quite long, let’s save the Tmux part for next time!