How Fish and I met¶
fish is a shell that emphasizes "user-friendliness" and discoverability. Its name stands for "friendly interactive shell". You wouldn't expect based on that that it'd be something for experienced Unixers, but it absolutely is.
Around the same time I learned Go, fish was suggested to me, so I tried it out and initially shared the referer's opinion that it was awesome. I was a happy user for about a year. Then I had some bad experiences with fish, ran into some things fish actually had solutions for but I didn't know about them, and ended up giving zsh a try.
I had about the same experience with zsh as with fish: I was thrilled to leave behind my frustrations with fish, learned zsh's upsides, then learned zsh's downsides, and ended up going back to fish to give it another try after having gained some wisdom. (In particular, my biggest gripe with fish had been the context-aware autocompletion - see below - and I realized that I could probably disable it.) I've stuck with fish ever since and I've learned to be quite happy with it.
Little configuration required¶
fish's best features all work out of the box: syntax highlighting, history-based autosuggestions, better tab completion (see below), and everything else I'm going to mention. Pretty much the only configuration you need to do is shell aliases, environment variables, and your prompt. Other shells can more or less get those things but require a lot of tinkering to do so.
Tab completion isn't restricted to the beginning of a word¶
Other shells I've used can only autocomplete a filename if you type the start of it. fish's autocompletion is more advanced. From what I can tell, the algorithm is:
If what you've typed matches the beginning of any filenames, autocomplete to that one or the biggest prefix common to all of them. If there's no prefix common to all of them after what you've typed, fail.
If what you've typed matches a contiguous part of any filename, even if it's not at the beginning, autocomplete to that one, or if there's more than one, fail.
If any filenames contain the characters you've typed in order, try to autocomplete to that one, or if there's more than one, fail.
To be fair, this feature isn't a strict positive. There are some times when it results in unintended autocompletions where other shells wouldn't.
saw exist. If I mean to type
rm q but my finger slips
and I type
rm a, and then press tab and enter instantly because I'm confident that the autocompletion
will work, fish completes me to
saw - because that's the only filename in the directory with an
a in it - and I end up removing the wrong file. But the times it helps far outweigh the times it
hurts in my experience.
Better response on failed autocomplete than any other shell¶
fish shows the list of possibilities after the first failed tab press, instead of requiring a second. I can't think of any rationale for not doing this.
fish is also the only shell I've seen that lets you use tab and shift-tab to navigate through the list of possibilities with the current option highlighted and even gives you a description of each item (for a file, it's type and size; for a command, the synopsis from the man page). Every other shell I've seen is missing at least one of the latter two facets of this feature, at least without heavy manual tinkering.
fish has a glob sequence that I don't know of existing in any other shell:
** matches any string of
characters even including a
/, so basically it's like
) but without the
clunk of embedding a subcommand. I can't express how many times I've benefitted from this convenience.
Manual pages for builtins¶
Thank the source! Other shells I've used don't have manual pages on builtins like
cd; they just
man builtin. The page for
bash itself does explain most of them but
for the love o' Unix,
man pages are such an antiquated primitive system for documentation
that putting all shell builtins and a complete description of shell grammar in one
manual page is like saying, "We'll pay you $100 to not read the documentation", because that's how desirable
it makes it to read. Why not put the whole source code for bash in one file while you're at it? There's a
reason we don't do that.
Thankfully, fish comes to the rescue and has
man pages for most builtin commands, in addition
to a builtin named
help that opens locally-stored HTML documentation on the shell's mechanics
in your browser!
There's no way to group commands¶
Since fish uses
() for command substitution and treats
` as a normal character and
$() as invalid syntax, there's no easy way to group commands. Imagine you want to pipe
cmd1; cmd2 into
cmd3. I don't think there's a way to do it at all in fish. I think you
have to resort to a programming language.
The idea of "context-aware autocompletion" sounds awesome until you use it for a few months.¶
The other big difference between fish autocompletion and other shells', and the one where fish doesn't have the good side of the comparison, is that fish tailors its autocompletion to the command, meaning tab doesn't behave the same for different commands.
The fundamental problem with this is that intelligent autocompletion is only useful to me insofar as I knows how it works. If I don't know how my tools work, I can't predict their behavior, and so I can't pre-schedule more keypresses until after I've seen what my tab press did. Worse, more often I forget this and try to pre-schedule following keypresses only to realize my tab press didn't do what I thought it would and I'm looking at a messed up string of characters that causes me a delay before I even start backspacing them. Multiple seconds can be wasted each time fish surprises me with its "smart" autocompletion, which it still was on a regular basis after probably a year of using it exclusively.
The worst part of the context-aware autocompletion is the crazy shit fish does in a
git repo. While
it's nice to be able to type just the first letter of the first component of a long path with
and have fish spell out the entire path for me, it's not necessary in most cases because
git add (and
most if not all similar commands) works on directories. There are even some times when it actually can't do things
normal tab completion can. When I try to
git add a file that already has staged changes, fish won't
autocomplete it even if it's the only possibility. Sometimes I hit tab multiple times and then stare at the
command for an entire 1-2 seconds wondering why it won't work. Did I misspell something? Did I inadvertenly
remove the file at some point? A common scenario to play out after that is that I backspace the last component
of the path and tab hoping to see the automatic pseudo-
ls output, but fish autocompletes me to a
different file in the directory.
Of course, it's possible to delete all of fish's contextual autocompletions (on FreeBSD I just
rm /usr/local/share/fish/fish_completions/*) and that's why I made this an h2 element.
I didn't remove the criticism because "disable the feature by deleting stuff" isn't a satisfactory answer to
"this feature that's on by default and can't be configured off as far as I could find is horrid".
Zsh and bash both have these. They're not the most useful feature ever, but I miss them from time to time.
Fish tab completion or accepting suggestions will case-correct what you've typed in a lot of situations. Sometimes this is actually pretty neat, like for environment variables (since they're usually named in all caps). But what's truly horrid about this feature is that if you type letters and fish thinks they're the wrong case, it'll actually display them as the "corrected" case even before you press tab or accept a suggestion. But then, of course, if you enter the command without pressing tab, it receives the letters as the case you typed. So fish's command line is a liar.
Another issue with the case correction is exemplified by an experience I had recently.
st TODO * in my history (
st is my
grep alias -
'search text'), but
st todo * more recently. I type
st TODO - I was holding down shift -
then accept suggestion to get the
*, and I get
st todo *. I had typed the right thing
and fish incorrected my case.
Artificially prevents the
= syntax for modifying the environment¶
I say it's artificial because the developers actually implemented the code they'd need to make it work:
> LSCOLORS=test ls fish: Unsupported use of '='. To run 'ls' with a modified environment, please use 'env LSCOLORS=test ls…'
So they literally wrote the code to parse the syntax and determine what you want to do so they could tell you that they don't support it. They implemented the feature so fish could tell you it isn't supported. Damn.
They disallow command substitution in the same way: any attempt to use a command substitution as the command
instead of as an argument is met with
fish: Command substitutions not allowed, even though they
obviously have the code to make this work. But I don't think this is as big of deal because it's almost never useful.
Verdict: fish is a good shell. I'd hate to go back to bash or even zsh now. I absolutely recommend fish.