
Not into heavily configuring your terminal? Right out of the box, the fish shell will transform your CLI experience.
A version of this post originally appeared on viget.com
This is article is part of a series on interactive shells. The next article in the series is Try Out Fish For Your Command Line Shell.
Apple has switching to zsh for the default macOS shell. Zsh has a lot to recommend it, and if the increased attention leads to zsh support in shellcheck zsh scripters everywhere will be thrilled. The downside of zsh is it leaves interactive shell (that is, command line experience, as opposed to writing scripts) configuration up to the use. No fancy user experience like the anomolously named fish shell provides by default, no pretty prompt like in GitHub README screenshots, and no guidance about how to customize things. You do get that sweet sweet zsh array syntax immediately, but as the manual acknowledges that's not what everyone comes for: the discussion of arrays begins,
"This chapter will appeal above all to people who are excited by the fact that print ${array[(r)${(l.${#${(O@)array//?/X}[1]}..?.)}]}
prints out the longest element of the array $array
. For the overwhelming majority that forms the rest of the population…"
(Zsh arrrays are so cool!) This guide is for that majority. We will see how to give the interactive terminal a highly functional prompt and will configure zsh to
@{-N}
syntax!some/path
as shorthand for cd some/path
!omm
+UP↑ will find my command
in the history!If you are not on Catalina or higher, zsh is not already your default shell. Either way, the copy of zsh available on macOS by default is that one that shipped with the system, which will get out of date. So the first thing to do is to install the latest version, with Homebrew.
These are the steps to follow if you need to install Homebrew. Run brew --version
to see if you have it already.
Download Xcode from the App Store https://itunes.apple.com/us/app/xcode/id497799835?mt=12
Download the Xcode command line tools from https://developer.apple.com/download/more/?=command line tools. If you don't have a developer account, you'll need to sign up for one (it's free).
Install Homebrew by running the command shown at https://brew.sh/.
Install zsh with Homebrew
Install zsh
shell
brew install zsh
macOS only allows certain shells to be used as the default shell. They are listed in /etc/shells
. Either add the following Homebrew zsh line to the bottom of /etc/shells
manually (Sublime directions; VS Code directions; Atom directions)
shell
/usr/local/bin/zsh
or simply run the command
shell
sudo echo /usr/local/bin/zsh >> /etc/shells
Note the double >>
! A single >
will overwrite the file, whereas >>
will append to it. Overwriting this file may damage your system. Make sure to append.
Set up the PATH
variable so that /user/local/bin/zsh
(the Homebrew copy of zsh) will be found before /bin/zsh
(the system copy of zsh). Either add this line to ~/.zshrc
manually:
shell
/usr/local/bin:$PATH
or simply run
shell
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc
Make the Homebrew copy of zsh your default shell
shell
chsh -s /usr/local/bin/zsh
Open a new terminal, and you should see the zsh prompt! The %
prompt character is a giveaway, and you can confirm by running echo $SHELL
and which zsh
- both should return /usr/local/bin/zsh
.
Zsh has a huge number of options. See them all at http://zsh.sourceforge.net/Doc/Release/Options.html. The following configuration is a good place to start. Either add everything between echo '
and ' >> ~/.zshrc
(or echo "
and " >> ~/.zshrc
) to ~/.zshrc
manually, below the PATH
changes, or simply run
shell
echo '# History[ -z "$HISTFILE" ] && HISTFILE="$HOME/.zsh_history"' >> ~/.zshrc
shell
echo "HISTSIZE=50000SAVEHIST=10000setopt extended_historysetopt hist_expire_dups_firstsetopt hist_ignore_dupssetopt hist_ignore_spacesetopt inc_append_historysetopt share_history# Changing directoriessetopt auto_cdsetopt auto_pushdunsetopt pushd_ignore_dupssetopt pushdminus# Completionsetopt auto_menusetopt always_to_endsetopt complete_in_wordunsetopt flow_controlunsetopt menu_completezstyle ':completion:*:*:*:*:*' menu selectzstyle ':completion:*' matcher-list 'm:{a-zA-Z-_}={A-Za-z_-}' 'r:|=*' 'l:|=* r:|=*'zstyle ':completion::complete:*' use-cache 1zstyle ':completion::complete:*' cache-path $ZSH_CACHE_DIRzstyle ':completion:*' list-colors ''zstyle ':completion:*:*:kill:*:processes'list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01'# Othersetopt prompt_subst" >> ~/.zshrc
See Zsh documentation 16.2.4: History for documentation.
By default zsh does not write history to a file. We set it to write history to ~/.zsh_history
. And then:
See Zsh documentation 16.2.1: Changing directories for documentation.
We make it possible to switch to a previous directory with cd -<directory stack position>
, where the directory stack position
is how many directories ago you want to step back. This closely mimics Git's @{-N}
"previous HEAD" notation. In the following examples, note that auto_cd
(see above) is assumed.
shell
~% ~/dir1~/dir1 % ~/dir2~/dir2 % cd -2~ %
Use dirs -v
to see the directory stack:
shell
~ % ~/dir1~/dir1 % dirs -v0 ~/dir11 ~% ~/dir2 && ~/dir3 && ~/dir4 && ~/dir2~/dir2 % dirs -v0 ~/dir21 ~/dir42 ~/dir33 ~/dir24 ~/dir15 ~% cd -3% dirs -v0 ~/dir21 ~/dir22 ~/dir43 ~/dir34 ~/dir15 ~%
N.b: changing to a directory in the directory stack moves the selected directory stack entry to the top of the stack, rather than creating a new entry. The last dirs -v
above was not the following:
shell
0 ~/dir21 ~/dir22 ~/dir43 ~/dir34 ~/dir2 # nope!5 ~/dir16 ~
Some will want to also setopt pushd_ignore_dups
, which prevents duplication in the directory stack. The resulting behavior would be:
shell
~% ~/dir1 && ~/dir2 && ~/dir1~/dir1% dirs -v0 ~/dir11 ~/dir22 ~
~/Downloads
for cd ~/Downloads
.cd
to in a session.-N
instead of the default +N
(e.g. cd -2
as opposed to cd +2
).See Zsh documentation 16.2.2: Completion for documentation.
add
, hit LEFT←, and then TAB⇥, the cursor will move to after the second d
and completions will be shown for add
.)AUTO_MENU
is set.cd ~/downl
TAB⇥ will autocomplete as ~/Downloads
. See Zsh documentation 22.37 The zsh/zutil Module for documentation.There are a huge number of plugins for zsh. https://github.com/unixorn/awesome-zsh-plugins#plugins and https://github.com/unixorn/awesome-zsh-plugins#completions are great lists. Here we install four that are practically essential. As above, note the double >>
s!
To add plugins you need a plugin manager. There are many out there; good options are Antibody, zplug, and Antigen. Antibody is currently the best option for most users: it has simple syntax and it's blazing fast*. zplug is great for power users, with its support for commands, binaries, conditional loading, custom sources, and local files, but it loads noticeably slower than Antibody. Antigen is slower than Antibody and zplug and it requires that plugin authors add special support for it, but it achieved early success and is mentioned in many plugin READMEs; you can do better these days, but you also can't get away without mentioning it.
(As you get into zsh management, you'll also see Oh My Zsh and Prezto. Both are "zsh frameworks", huge collections of settings, customizations, aliases, and scripts, with support for managing themes. Installing either will significantly increase the time it takes to load a new terminal, and will provide much more there than most users need. Advanced users will enjoy digging through the source looking for useful bits.)
It's easy to switch from one plugin manager another, so don't sweat the decision.
As above, run these commands or manually add everything between echo '
and ' >>
/ between echo "
and " >>
Either use Antibody:
shell
brew install getantibody/tap/antibody
shell
echo 'zsh-users/zsh-autosuggestionszsh-users/zsh-completionszsh-users/zsh-history-substring-searchzsh-users/zsh-syntax-highlighting' >> ~/.zsh_plugins.txt
shell
antibody bundle < ~/.zsh_plugins.txt > ~/.zsh_plugins.sh
shell
echo 'source ~/.zsh_plugins.sh' >> ~/.zshrc
N.b: With this setup,
shell
antibody bundle < ~/.zsh_plugins.txt > ~/.zsh_plugins.sh
must be run again any time a change is made to ~/.zsh_plugins.txt
. See the Antibody Install docs for alternative methods, but note that this two-file method has the fastest load time.
or use zplug:
shell
brew install zplug
shell
echo '# Init zplugexport ZPLUG_HOME=/usr/local/opt/zplug# Homebrew-installed zplugsource $ZPLUG_HOME/init.zsh# Pluginszplug "zsh-users/zsh-autosuggestions"zplug "zsh-users/zsh-completions"zplug "zsh-users/zsh-syntax-highlighting", defer:2zplug "zsh-users/zsh-history-substring-search", defer:3# Install plugins if there are plugins that have not been installedif ! zplug check --verbose; thenprintf "Install? [y/N]: "if read -q; thenecho; zplug installfifi# Load zplugzplug load' >> ~/.zshrc
or use Antigen:
shell
brew install antigen
shell
echo '# Pluginsantigen bundle zsh-users/zsh-autosuggestionsantigen bundle zsh-users/zsh-completionsantigen bundle zsh-users/zsh-history-substring-search# Apply Antigenantigen apply' >> ~/.zshrc
Configure zsh-history-substring-search. Note the single quotes vs double quotes.
shell
echo "# zsh-history-substring-search key bindingsbindkey '^[[A' history-substring-search-upbindkey '^[[B' history-substring-search-down" >> ~/.zshrc
Then open a new terminal. Antibody users may get an error. Running antibody update
will download any missing plugins.
This plugin will suggest previously run commands as you type. Accept the suggestion by pressing the right arrow key.
https://github.com/zsh-users/zsh-autosuggestions
Completions for the commands available within apps, with descriptions. Trigger it with TAB⇥.
https://github.com/zsh-users/zsh-completions
By default, history searches only match the entire command. Typing i
and then UP↑ will finds history entries that start with i
(if true; then echo yes; fi
would be a match, echo hi
would not). This plugin find through all history entries that contain the input string anywhere (both would be matches).
This guide configures the UP↑ and DOWN↓ arrows to cycle through history, but you can choose any keys you like. See the project homepage for more info.
https://github.com/zsh-users/zsh-history-substring-searchzsh-syntax-highlighting
Colors commands as you type them, making errors easy to spot.
There is a huge world of custom prompts —aka "themes"— for zsh interactive shells. Two popular themes are Spaceship and Powerlevel9k.
Themes can be just fun, or just pretty, but at their best they make you work fitter, happier, more productive by surfacing the information you need.
Powerlevel9k shows Git status information, time, and more, in the Powerline model of color-coded bars:
Spaceship is can show all sorts of things, and you choose which it should show. It has out of the box support for Git and Mercurial, can display the current project's versions of various tech, batter life, and much more.
My own command prompt is a modified version of Spaceship that brings in ideas from oh-my-git and git-radar. It shows the time and the current directory. If I'm in a Git repo it shows the status, information about the checked out branch's upstream, any tags at HEAD
, and whether there are files with the --assume-unchanged
or --skip-worktree
bits set. If Node or Ruby are applicable it shows the current version. If my battery is low, it warns me. It's hard to remember how I managed before having all of that information right in front of me.
Whatever tech you use, someone has probably written a zsh prompt theme to make your life better. It might not be Powerlevel9k or Spaceship. Find the flavor of the month by searching GitHub for "zsh prompt";There are screenshots of many more at https://github.com/robbyrussell/oh-my-zsh/wiki/External-themes, and awesome-zsh-plugins lists more than you would want to try out.
Trying a theme out is easy. Add the snippet for the your zsh configuration manager, and then open a new terminal or run source ~/.zshrc
. Here's how to add Spaceship or Powerlevel9k; check out their sites to learn about their settings.
Either
shell
# ~/.zsh_plugins.txt for Antibodydenysdovhan/spaceship-prompt# orbhilburn/powerlevel9k
and then
shell
antibody bundle < ~/.zsh_plugins.txt > ~/.zsh_plugins.sh
or
shell
# for zplug: add above the missing plugins check in ~/.zshrczplug "denysdovhan/spaceship-prompt", use:spaceship.zsh, from:github, as:theme# orzplug "bhilburn/powerlevel9k", use:powerlevel9k.zsh-theme, from:github, as:theme
or
shell
# for Antigen: add above `antigen apply` in ~/.zshrcantigen theme robbyrussell/oh-my-zsh themes/robbyrussell# orantigen theme denysdovhan/spaceship
The first sentence on the zsh homepage is, "Zsh is a shell designed for interactive use, although it is also a powerful scripting language." We've seen some of what makes zsh great for interactive use. If you write shell scripts and are not limited by Bash (or POSIX) compatibility requirements, take it for a spin!
site:https://www.zsh.org/mla/
to Google searches to search the zsh mailing list archive.Not into heavily configuring your terminal? Right out of the box, the fish shell will transform your CLI experience.
Test packages that have not been published to a registry, without getting caught in the pitfalls of npm and yarn's built-in solutions.