I’ve used bash for a long time and zsh with a plugin manager for a few years. My .zshrc had slowly
turned into something I was afraid to touch, so I gave fish a proper
month instead of the usual afternoon.
Short version: I’m keeping it for interactive use. Scripts stay in bash.
What I like
Good defaults. Autosuggestions from history, syntax highlighting, and completions generated from man pages all work out of the box. That replaced four zsh plugins.
Abbreviations instead of aliases. An abbreviation expands in place when you hit space, so your history contains the real command:
abbr -a gs git status
abbr -a gco git checkout
abbr -a k kubectl
Functions are just files. Anything in ~/.config/fish/functions/name.fish is autoloaded the first
time you call it. funced and funcsave make quick edits painless:
function mkcd
mkdir -p $argv[1]; and cd $argv[1]
end
funcsave mkcd
PATH handling. fish_add_path deduplicates and persists, so no more PATH growing on every
nested shell:
fish_add_path ~/.local/bin
What takes getting used to
fish is not POSIX. Variables use set (there’s an export wrapper, but set -gx is the native
way), and $(...) only arrived in fish 3.4, so I just use (...):
set -gx EDITOR nvim
set files (ls *.md)
Sourcing a bash script that sets environment variables doesn’t work directly. For the two tools that need it, I either use the fish integration they ship (most do now) or run them through bash.
My setup
My whole config.fish is now about 30 lines. Most of it is abbreviations and a couple of
status is-interactive guards. Compared to the old zsh setup, startup is faster and I actually know
what every line does.
If you’ve bounced off fish before because a copy-pasted command from Stack Overflow didn’t work, the
trick is simple: keep #!/usr/bin/env bash for scripts, and type bash for the rare one-off.