A version of this post appeared on viget.com
This is article is the second in a two-part series on interactive shells. The first is Configure Zsh Options & Plugins for Productivity in macOS's Default Shell.
🆕 February 22, 2020: Abbreviations are now available in zsh. Check out my plugin zsh-abbr!
In the article Configure Zsh Options & Plugins for Productivity in macOS's Default Shell I walked through customizing zsh's interactive shell to add what I consider key features. (An "interactive shell" is the shell as experienced in a terminal, as opposed to when writing shell scripts.) In the end we had nice history management, a shorthand for
cd, the ability to jump to a directory we were in several
cds back, command completion with an interactive menu, inline suggestions, completions for various programs, an intuitive way to filter history, syntax highlighting in the terminal, and an informative prompt. In short, the result was interactive shell that felt smart and helped us find and do things more quickly without any need to learn additional tools.
But getting zsh there took significant configuration. There are other approaches — Oh My Zsh, for example, is an enormous collection of zsh configurations you install in one go. Oh My Zsh did a lot to popularize zsh as a shell for people who wanted a great interactive shell experience and snazzy prompt. But it includes more customization and extension than most people need or will even discover, and as each of those confers a (usually small) performance hit some long-term users end up switching to a more paired-down configuration — that's what the previous post is.
There's a much easier way: switching to fish. fish is a shell that puts the command line experience first. Out of the box it comes with almost everything in my recommended zsh setup circa 2019, and a lot more. In fact, many of the plugins in that zsh setup are inspired by built-in fish features, and they don't always live up to the original.
zsh is still a wonderful shell. Its comprehensive builtins and elegant syntax make all kinds of cool programming tight. But if you spend much time running commands in a terminal, try fish there.
fish is available for macOS, Linux, BSD, Windows, and as source. It's probably distributed in whatever way you expect packages for your system to be distributed in. There are links on the fish homepage (note: the homepage might be too cute for its own good. If you're looking for a starting guide, jump straight to the Tutorial).
On macOS, you can install fish with Homebrew:
% brew install fishCopied
and then make it available to the system by adding
/usr/local/bin/fish to your
Installing fish doesn't commit you to using it. You can switch an individual session to fish by running
fish. Any other open sessions (i.e. other open terminal windows) and any other sessions you start won't be affected. If you want to get out of your fish session and back to the default shell you started in, just run
If you're coming from bash, fish will automatically import your history (and here's the procedure for merging bash history into an existing fish history). If you're coming from zsh and want to migrate your history over, there's rsalmei/zsh-history-to-fish (and here's the procedure for merging zsh history into an existing fish history).
If you decide to make fish your default shell, run
% chsh -s /usr/local/bin/fishCopied
Here's a rundown of the features in my recommended zsh setup circa mid-2019. Tl;dr: fish does it out of the box. All zsh configuration instructions are given in Configure zsh Options & Plugins for Productivity in macOS's Default Shell.
|Save history to a file||Yes!||If configured|
|Trim history||Yes! Limited to 256K unique commands (which is a ton). Least recently used are trimmed first||If configured. Trims from end. Can configure to trim duplicates before uniques.|
|Timestamp history records||Yes!||If configured|
|Dedupe the history||Yes!||If configured|
|Keep space-prefixed commands out of the history||Yes!||If configured|
|Share history across open terminals||If configured. Configure manually or with a plugin manager||If configured|
|Navigate directory stack||Yes! With browser-like back/forward or by index||If configured, by index. No browser-like "forward" - previous directory is always top-of-stack|
|Command completions (with TAB)||Yes! First TAB inserts the top-ranked completion. Second opens an interactive menu of all possibilities. Menu clears if you change the input or esc||If configured. Depending on configuration, TAB either cycles through inserting the possibilities or opens an interactive menu. If you change the input, menu persists until you TAB again but is unusable|
|Case insentivity||"Case correcting." fish has a multilevel ranking of how good a match something is. Key takeaway is exact matches are prefered over wrong-case matches. Decides whether to show multiple suggestions based on the strength of the available matches.||If configured. Highly customizable.|
|Color coded completion menus||Yes! Plus additional context such as object type and size||If configured|
|Suggestions as you type||Yes! Based on history and completions, and will not suggest commands that are in history but which are no longer available||With a plugin. Plugin does not filter out commands that are no longer available|
|Program command completions (e.g.
||Yes! For common programs. Plus simple syntax for writing your own completions, and simple support for developers to distribute completions along with software||Yes! For common programs. Add plugins to add completions not shipped with zsh. Writing completions requires shell scripting knowledge|
|History substring search||Yes! For example type
||With a plugin. Binding to arrow key navigation requires additional configuration|
|Syntax highlighting||Yes!||With a plugin|
|Selection of prompts||Yes! Ships with over a dozen. Can install more, or write your own||Can install or write your own|
fish has a bunch of things going for it that other common shells don't. Here are a few that jump out:
Startup and each new prompt are fast! The fish maintainers have invested time in making the interactive shell experience load quickly.
History search, with globbing! Run
history search "my command" to search for instances of "my command" in your history. Or run
history search "my*gl?b" to search for instances of "my*gl?b".
🆕 Now for zsh too! Check out my plugin zsh-abbr! Abbreviations! They're similar to standard aliases, but they expand inline. If you have the abbreviation "gc" for "git commit",
gco<space></space> will immediately be replaced with
git commit and
gco<enter></enter> will be replaced with
git commit and the command will be run. This means you can save keystrokes without forgetting the full command, and your history will be more meaningful. Adding abbreviations is simple: to create that "gc"/"git commit" abbreviation, run
abbr gc git commit. More options are available — run
abbr --help to see them.
Discussion and contribution happen on GitHub, making the bar for entry low (zsh is mailing list-based; bash has a Savane tracker; ksh is on GitHub but currently the few maintainers have their hands full with modernizing the codebase)
Web UI for essential configurations. fish really doesn't want you to have to be a shell scripter to fine tune your interactive shell. Run
to open a browser window where you can
I still enjoy digging through the zsh docs and spending weekends fiddling with the configuration. If that isn't you, check out fish. It's like like switching from dollar store foam headphones to nice noise-cancelling ones, except free: you do nothing, and suddenly things you didn't know could be better are.