A while ago, I switched from bash
to fish
. This time, I stuck with it. After reading this, you should have a better idea of the hassles involved with switching to a new shell.
(I’m going to assume that you know what a shell is. While I don’t expect you to have used anything other than bash
, I assume you’ve heard of other shells like zsh
and tcsh
.)
Even an old version of bash
is good enough
Let’s get this out of the way: bash
ain’t half bad. command.com
, used for both DOS and Windows, didn’t even do command tab completion. cmd.exe
, used in Windows NT up through Windows 10, only started doing command completion by manually changing a setting. bash
does this out of the box. Furthermore, even an old version of bash
, like the 3.2 that ships with macOS 10.14 Mojave, does all this. (Every so often I check bash
’s NEWS file; none of the additions seem memorable enough to justify switching to a new version.)
bash
also has nifty-keen substitution parameters like !!
and !$
. They’re handy.
Initially everything is just a little bit wrong
As you might expect, a fresh fish
shell is way different from my moderately-customized bash
settings. fish
color settings aren’t too important, but I’m used to my style of prompt and I didn’t want much, if anything, to change about it. The biggest annoyance? fish
automatically shortens paths in prompts. That is, ~/Pictures/Funny/Autocorrect Failures
would get shortened to ~/P/F/Autocorrect Failures
and I’d have to type pwd
to reorient myself in the file system hierarchy. Piecing together different prompts and their writing styles, I eventually settled on this functions/fish_prompt.fish
:
function fish_prompt --description 'Write out the prompt'
set -l last_status $status
# `set -U ARROW_ONLY yes` and `set -eU ARROW_ONLY` to toggle
# good for presentations
if test -n "$ARROW_ONLY"
echo '> '
return
end
if test $last_status -ne 0
printf "%s(%d)%s " (set_color red --bold) $last_status (set_color normal)
end
set -l color_cwd
set -l suffix
switch "$USER"
case root toor
if set -q fish_color_cwd_root
set color_cwd $fish_color_cwd_root
else
set color_cwd $fish_color_cwd
end
set suffix '#'
case '*'
set color_cwd $fish_color_cwd
set suffix '>'
if set -q fish_private_mode
set suffix '»'
end
end
# set $fish_prompt_pwd_dir_length to 0 to disable shortening in prompt_pwd
echo -n -s \
(set_color --bold $fish_color_user) \
"$USER" @ (prompt_hostname) \
' ' \
(set_color --bold $color_cwd) \
(prompt_pwd) \
(set_color normal) \
" $suffix "
end
It’s a bit fancier than my old $PS1
, but it’s still reasonably clear to read for a novice like me. While the documentation for $fish_prompt_pwd_dir_length
wasn’t easy to find, everything else was. This was a refreshing change compared to what I remember of bash
documentation. With bash
’s web-based info pages, I never seemed to be able to find how to do anything I wanted and had to rely on other sites to explain bash
’s feature set to me.
Settling in
The fish
documentation talks about universal variables. At first blush, variables that are shared between all instances of a shell sounded like a useful idea. Every so often I’ve wanted to change something about bash
and had to paste-and-run source ~/.bashrc
in all my running bash
instances. This is an annoyance, if an infrequent one. Unfortunately, I found it’s much more useful to be able to put my variable settings — and especially additions — in config.fish
, the .bashrc
/.bash_profile
equivalent. To see why, have a look at a snippet of my fish_variables
:
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR __fish_init_2_39_8:\x1d
SETUVAR __fish_init_2_3_0:\x1d
SETUVAR __fish_init_3_x:\x1d
SETUVAR fish_color_autosuggestion:BD93F9
SETUVAR fish_color_cancel:\x2dr
SETUVAR fish_color_command:normal
# …
SETUVAR fish_pager_color_prefix:white\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan
SETUVAR fish_prompt_pwd_dir_length:0
Now imagine trying to edit your $PATH
if it were separated by not just colons, but backslash-escaped UTF-8 byte sequences of Private Use Area code points. Ick. Meanwhile, the old-fashioned way preserves the ability to logically group similar path adjustments:
set -x PATH $HOME/Library/Python/2.7/bin $PATH
set -x PATH /Library/Frameworks/Python.framework/Versions/2.7/bin $PATH
set -x PATH /Library/Frameworks/Python.framework/Versions/3.5/bin $PATH
set -x GOPATH $HOME/Projects/Go
set -x PATH $HOME/Projects/Go/bin $PATH
set -x PATH $HOME/bin $PATH
This is doubly important for anything that needs to be documented on a per-chunk basis:
# for xterm-256color:
#
# - 38;5 — next number sets text color
# - 48;5 — next number sets background color
#
# (colors at, say, <https://i.stack.imgur.com/UQVe5.png>)
set -x EXA_COLORS
set -a EXA_COLORS "di=38;5;69" # directories
set -a EXA_COLORS "da=38;5;195" # times
set EXA_COLORS (string join ":" $EXA_COLORS)
While it’d be neat to have exa colors propagate instantly, editing its fish_variables
entry would be as fun as editing a 120-column, one-line regular expression.
Yes, this is a lot of busywork
If all this sounds like I’m doing lots of work to merely get my fish
setup to be as good as my bash
setup, you’re right. What’s more, I hadn’t really seen any of the benefits of fish
yet.
Eventually, I found a lot of things to like about fish
that made me glad I switched.