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 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:

  1. 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.

  2. 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.

  3. 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.

This means if you have a number of files that start with the same few characters, you can skip that part and just type the first letter or two that's different. In another shell, you'd have to type at least the first character of the common part and autocomplete that first.

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.

Example: qw and 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.

** globs

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 (find...) 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 god! Other shells I've used don't have manual pages on builtins like cd; they just redirect to 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. Or use cmd1; cmd2 as a single command substititon? I think you can accomplish that one by running it first and storing the output in a variable, but that's a clunky workaround.

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 git add 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".

No [] globs

zsh and bash both have these. They're not the most useful feature ever, but I miss them from time to time.

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 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.



Comments

You don't need an account or anything to post. Accounts are only for email notifications on replies. Markdown formatting is supported.