Apple has announced that in macOS 10.15 Catalina the default shell will be zsh
.
In this series, I will document my experiences moving bash
settings, configurations, and scripts over to zsh
.
- Part 1: Moving to zsh
- Part 2: Configuration Files
- Part 3: Shell Options
- Part 4: Aliases and Functions
- Part 5: Completions (this article)
- Part 6: Customizing the
zsh
Prompt - Part 7: Miscellanea
- Part 8: Scripting
zsh
I am preparing a book on this topic, reworked and expanded with more detail and topics. Like my other books, I plan to update and add to it after release as well, keeping it relevant and useful. You can pre-order it on the Apple Books Store now.
As I have mentioned in the earlier posts, I am aware that there are many solutions out there that give you a pre-configured ‘shortcut’ into lots of zsh
goodness. But I am interested in learning this the ‘hard way’ without shortcuts. Call me old-fashioned. (“Uphill! In the snow! Both ways!”)
What are Completions?
Man shells use the tab key (⇥) for completion. When you press that key, the shell tries to guess what you are typing and will complete it, or if the beginning of what you typed is ambiguous, suggest from a list of possible completions.
For example when you want to cd
to your Documents
folder, you can save typing:
% cd ~/Doc⇥
% cd ~/Documents/
When you hit the tab key, the system will complete the path to the Documents
folder.
When the completion is ambiguous, the shell will list possible completions:
% cd ~/D⇥
Desktop/ Documents/ Downloads/
At this point, you can add a character or two to get to a unique completion, and hit the tab key again. In zsh
you can also hit the tab key repeatedly to cycle through the suggested completions. In this example, the first tab keystroke will show the list, the second will complete ~/Desktop/
, the third completes ~/Documents
, and so on.
You can use tab completion commands as well:
% system⇥
system_profiler systemkeychain systemsetup systemsoundserverd systemstats
% system_⇥
% system_profiler
Not having to type path and file names saves time and avoids errors, especially with complex paths with spaces and other special characters:
% cd ~/Li⇥
% cd ~/Library/Appl⇥
% cd ~/Library/Application S⇥
Application Scripts/ Application Support/
% cd ~/Library/Application Su⇥
% cd ~/Library/Application Support/
Using tab completion is a huge productivity boost when using a shell.
Turning It On
In the default configuration, tab completion in zsh
is very basic. It will complete commands and paths, but not much else. But you can enable a very powerful, and useful completion system.
zsh
comes with a tool you can use to setup this completion system. When you run the compinstall
command it will lead you through a complex and hard to understand list of menus which explains the options and will generate the code necessary to set this configuration up and add it to your .zshrc
file or another configuration file of your choice.
Since the commands to configure the completion are quite arcane and hard to understand, this is a good way to get something to start out with. I will explain some of these options and commands in detail.
Whether you use compinstall
or not, to turn on the more powerful completion system, you need to add at least this command to your zsh configuration file:
autoload -Uz compinit && compinit
This will initialize the zsh
completion system. The details of this system are documented here.
If you want to configure the system, the configuration commands (usually zstyle
commands) should be added to the zsh
configuration file before you enable the system. (This only matters for a few configurations, but as a general rule it is safer.)
All of these completion rules need to be loaded and prepared. zsh
’s completion system creates a cache in the file ~/.zcompdump
. The first time you run compinit
it might take a noticeable time, but subsequent runs should use this cache and be much faster.
Sometimes, especially when building and debugging your own completion files, you may need to delete this file to force a rebuild:
% rm -f ~/.zcompdump
% compinit
Case Insensitive Completion
Since the macOS file systems are usually case-insensitive, I prefer my tab-completion to be case-insensitive as well. For bash
you configure that in the ~/.inputrc
. In zsh
you modify the completion systems behavior with this (monstrous) command:
# case insensitive path-completion
zstyle ':completion:*' matcher-list 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]} l:|=* r:|=*' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]} l:|=* r:|=*' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]} l:|=* r:|=*'
I have seen many varieties for this configuration in different websites, but this is what compinstall
adds when I select case-insensitive completion, so I am going with that.
Partial Completion
This is a particularly nice feature. You can type fragments of each path segment and the completion will try to complete them all at once:
% cd /u/lo/b⇥
% cd /usr/local/bin
% cd ~/L/P/B⇥
% ~/Library/Preferences/ByHost/
If the fragments are ambiguous, there are different strategies to what the completion system suggests. I have configured these like this:
# partial completion suggestions
zstyle ':completion:*' list-suffixes
zstyle ':completion:*' expand prefix suffix
Commands with built-in completion
zsh
comes with several completion definitions for many commands. For example, when you type cp
and then hit tab, the system will correctly assume you want to complete a file path and show the suggestions from the current working directory.
However, when you type cp -⇥
the completion can tell from the -
that you want to add an option to the command and suggest a list of options for cp
, with short descriptions.
% cp -⇥
-H -- follow symlinks on the command line in recursive mode
-L -- follow all symlinks in recursive mode
-P -- do not follow symlinks in recursive mode (default)
-R -- copy directories recursively
-X -- don't copy extended attributes or resource forks
-a -- archive mode, same as -RpP
-f -- force overwriting existing file
-i -- confirm before overwriting existing file
-n -- don't overwrite existing file
-p -- preserve timestamps, mode, owner, flags, ACLs, and extended attributes
-v -- show file names as they are copied
As the context of command prompt you are assembling changes, you may get different completion suggestions. For example, the completion for ssh
will suggest host names:
% ssh armin@⇥
zsh
comes with completion definitions for many common commands. Nevertheless, it can be helpful to just hit tab, especially when wondering about options.
On macOS completions are stored in /usr/share/zsh/5.3/functions
(replace the5.3
with 5.7.1
in Catalina). This directory stores many functions used with zsh
and is in the default fpath
. All the files in that directory that start with an underscore _
contain the completion definitions command. So, the file _cp
contains the definition for the cp
command. (Some of the definition files contain the definitions for multiple commands.)
Completions for macOS Commands
There are even a few macOS specific command that come with the default zsh
installation.
% system_profiler ⇥⇥
macOS High Sierra and macOS Mojave come with zsh
5.3, which is now nearly two years old. zsh
5.3 contains less macOS specific completion definitions than the current zsh
5.7.1 which will is the pre-installed zsh
in macOS Catalina. Some of the completions in 5.3 have also been updated in 5.7.1.
Tool | zsh 5.3 | zsh 5.7.1 |
---|---|---|
caffeinate |
√ | |
defaults |
√ | √ |
fink |
√ | √ |
fs_usage |
√ | |
hdiutil |
√ | √ |
mdfind |
√ | |
mdls |
√ | |
mdutil |
√ | |
networksetup |
√ | |
nvram |
√ | |
open |
√ | √ |
osascript |
√ | |
otool |
√ | |
pbcopy /pbpaste |
√ | |
plutil |
√ | |
say |
√ | |
sc_usage |
√ | |
scselect |
√ | |
scutil |
√ | |
softwareupdate |
√ | √ |
sw_vers |
√ | |
swift |
√ | |
system_profiler |
√ | √ |
xcode_select |
√ |
Load bash completions
Since the default shell on macOS has been bash
for so long, there are quite a few bash
completion definitions for macOS commands and third party tools available. For example Tony Williams’ bash
completion for autopkg
(post, Github).
You do not have to rewrite these completions, since the zsh
completion system can use bash
completion scripts as well: (add this to your zsh
configuration file)
# load bashcompinit for some old bash completions
autoload bashcompinit && bashcompinit
[[ -r ~/Projects/autopkg_complete/autopkg ]] && source ~/Projects/autopkg_complete/autopkg
When you have multiple bash
completion scripts you want to load, you only need to load bashcompinit
once.
Build your own completions
Once you start using completions, you will want to have them everywhere. While many built-in completions exists, there are still many commands that lack a good definition.
Some commands, like the swift
command line tool, have a built-in option to generate the completion syntax. You can then store that in a file and put it in your fpath
:
% swift package completion-tool generate-zsh-script >_swift
Note: in the case of
swift
, its definition will conflict with the_openstack
definition inzsh
5.3. You can fix this with the commandcompdef _swift swift
after loading the completion system.
Some commands provide a list of options and arguments with the -h/--help
option. If this list follows a certain syntax, you can get a decent completion working with
% compdef _gnu_generic <command>
One example on macOS, where this has decent results is the xed
command which opens a file or folder in Xcode.
But for best results, you will often have to build the description yourself. Unfortunately this is not a simple task. The syntax is meticulously, but also quite abstractly documented in the zsh
documentation for the Completion System. I also found the ‘howto’ documentation in the zsh-completions
repository very useful, as well as the ‘zsh
Completion Style Guide.’
To avoid everyone re-inventing the wheel, I have started a repository on Github for macOS specific completion files. The page has the instructions on how to install them and I will welcome pull requests with contributions. Since I am just starting to learn this as well, I am sure there are improvements that can be made on the completions I have built so far and there are several commands where you can test your skills and build a new one.
I suggest the #zsh
channel on the MacAdmins Slack for discussion.
Next
In the next post in this series, we will discuss how to configure zsh
’s command line prompt.