testing the fish shell
I have been testing fish for a couple months now (this file started on
2025-01-03T23:52:15-0500 according to stat(1)
), and those are my
notes. I suspect people will have Opinions about my comments here. Do
not comment unless you have some Constructive feedback to provide: I
don't want to know if you think I am holding it Wrong. Consider that I
might have used UNIX shells for longer that you have lived.
I'm not sure I'll keep using fish, but so far it's the first shell
that survived heavy use outside of zsh(1)
(unless you count
tsch(1)
, but that was in another millenia).
My normal shell is bash(1)
, and it's still the shell I used
everywhere else than my laptop, as I haven't switched on all the
servers I managed, although it is available since August 2022 on
torproject.org
servers.. I first got interested in fish because they
ported to Rust, making it one of the rare shells out there
written in a "safe" and modern programming language, released after an
impressive ~2 year of work with Fish 4.0.
Cool things
Current directory gets shortened,
~/wikis/anarc.at/software/desktop/wayland
shows up as
~/w/a/s/d/wayland
Autocompletion rocks.
Default prompt rocks. Doesn't seem vulnerable to command injection assaults, at least it doesn't trip on the git-landmine.
It even includes pipe status output, which was a huge pain to implement in bash. Made me realized that if the last command succeeds, we don't see other failures, which is the case of my current prompt anyways! Signal reporting is better than my bash implementation too.
So far the only modification I have made to the prompt is to add a
printf '\a'
to output a bell.
By default, fish keeps a directory history (but separate from the
pushd
stack), that can be navigated with cdh
, prevd
, and
nextd
, dirh
shows the history.
Less cool
I feel there's visible latency in the prompt creation.
POSIX-style functions (foo() { true }
) are unsupported. Instead,
fish uses whitespace-sensitive definitions like this:
function foo
true
end
This means my (modest) collection of POSIX functions need to be ported to fish. Workaround: simple functions can be turned into aliases, which fish supports (but implements using functions).
EOF heredocs are considered to be "minor syntactic sugar". I find them frigging useful.
Process substitution is split on newlines, not whitespace. you need to
pipe through string split -n " "
to get the equivalent.
<(cmd)
doesn't exist: they claim you can use cmd | foo -
as a
replacement, but that's not correct: I used <(cmd)
mostly where
foo
does not support -
as a magic character to say 'read from
stdin'.
Documentation is... limited. It seems mostly geared the web docs
which are... okay (but I couldn't find out about
~/.config/fish/conf.d
there!), but this is really inconvenient when
you're trying to browse the manual pages. For example, fish thinks
there's a fish_prompt
manual page, according to its own completion
mechanism, but man(1)
cannot find that manual page. I can't find the
manual for the time command (which is actually a keyword!)
Fish renders multi-line commands with newlines. So if your terminal looks like this, say:
anarcat@angela:~> sq keyring merge torproject-keyring/lavamind-
95F341D746CF1FC8B05A0ED5D3F900749268E55E.gpg torproject-keyrin
g/weasel-E3ED482E44A53F5BBE585032D50F9EBC09E69937.gpg | wl-copy
... but it's actually one line, when you copy-paste the above, in foot(1), it will show up exactly like this, newlines and all:
sq keyring merge torproject-keyring/lavamind-
95F341D746CF1FC8B05A0ED5D3F900749268E55E.gpg torproject-keyrin
g/weasel-E3ED482E44A53F5BBE585032D50F9EBC09E69937.gpg | wl-copy
Whereas it should show up like this:
sq keyring merge torproject-keyring/lavamind-95F341D746CF1FC8B05A0ED5D3F900749268E55E.gpg torproject-keyring/weasel-E3ED482E44A53F5BBE585032D50F9EBC09E69937.gpg | wl-copy
Note that this is an issue specific to foot(1), alacritty(1) and gnome-terminal(1) don't suffer from that issue.
Blockers
()
is like $()
: it's process substitution, and not a
subshell. This is really impractical: I use ( cd foo ; do_something)
all the time to avoid losing the current directory... I guess I'm
supposed to use pushd
for this, but ouch. This wouldn't be so bad if
it was just for cd
though. Clean constructs like this:
( git grep -l '^#!/.*bin/python' ; fdfind .py ) | sort -u
Turn into what i find rather horrible:
begin; git grep -l '^#!/.*bin/python' ; fdfind .py ; end | sort -ub
It... works, but it goes back to "oh dear, now there's a new langage again". I only found out about this construct while trying:
{ git grep -l '^#!/.*bin/python' ; fdfind .py } | sort -u
... which fails and suggests using begin
/end
, at which point: why
not just support the curly braces?
FOO=bar
is not allowed. It's actually recognized syntax, but creates
a warning. We're supposed to use set foo bar
instead. This really
feels like a needless divergence from standard.
Aliases are... peculiar. Typical constructs like alias mv="\mv -i"
don't work because fish treats aliases as a function definition, and
\
is not magical there. This can be worked around by specifying the
full path to the command, with e.g. alias mv="/bin/mv -i"
. Another
problem is trying to override a built-in, which seems completely
impossible. In my case, I like the time(1)
command the way it
is, thank you very much, and fish provides no way to bypass that
builtin. It is possible to call time(1)
with command time
, but
it's not possible to replace the command
keyword so that means a lot
of typing.
Again: you can't use \
to bypass aliases. This is a huge annoyance
for me. I would need to learn to type command
in long form, and I
use that stuff pretty regularly. I guess I could alias command
to
c
or something, but this is one of those huge muscle memory challenges.
alt . doesn't always work the way i expect.
You can use your Mastodon account to reply to this post.