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.
The sentence fragment: ‘you need to add at least this command to yourzsh’ has some typos.
Overall excellent serious / article. Used to come up to speed on zsh.
Will consider purchasing the book.
Thank you!
Hi, thanks for these articles.
I don’t understand this, it’s a comment not a command, and I don’t know what I’m supposed to do with it. Same on the section that follows.
# 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:|=*’
Can you please clarify? Does it get run at the command line? Does it get pasted into .zshrc? Not sure, info is sparse there.
Thanks,
Will
The zstyle command configures the behavior of the shell, in this case for path completion. I free it is non-obvious, but that’s what we have. And yes, you place this in your zahreicher file.
I wish somewhere someone would tell me how to get rid of smart completion and have what bash gives me after
complete -r
specifically, complete any path and file regardless of permissions or what a command expects. Just complete files, that can’t be so hard!
How do I disable all that unwanted complexitiy?
Not sure I exactly understand what you want. But when you make sure the
autoload -Uz compinit && compinit
command does not run in any of your zsh configuration files you should get the basic zsh auto-completion which sounds closer to what you want?What I am looking for is a setting that would make completion completely dumb. Complete only directory/file, and without looking at modes, at any point in the command line, without looking at context.
Example: I want to run bin/myprog. I type bi and hit tab. -Beep-. Because at the beginning of the command line, it doesn’t complete directories. Many other issues with completion make livign with zsh annoying. In bash, I can use ‘complete -r’ to remove all smart completion. I’m looking for the zsh equivalent.
Once you have run
compinit
to start the “new” context sensitive autocompletion, you cannot disable it anymore. If you don’t want it, you have to make sure that thecompinit
is not executed in your zsh configuration files. There are somezstyle
commands that configure the behavior of auto-completion which you should also remove.This may be a challenge if you are loading a configuration tool like oh-my-zsh or antigen for other purposes. If that is the case, you will have to consult that tool’s documentation.
You can also install bash5 and switch to using that when you prefer bash behavior.
I can’t use bash because my company has a host of scripts based on zsh that I need to use 🙁 so I need to find a way to live with it.
You don’t have to use zsh as the interactive shell to write, test, and run zsh scripts. The shell that runs or interprets a script is determined by the shebang in the first line of the script and independent of the interactive shell.
If the scripts from your org don’t have a shebang in the first line… they really should.
Sadly, it’s not scripts per se – and those don’t have a shebang. They also created an intricate web of aliases. The idea you gave me is a good one, though – I can probably wrap the scripts I commonly use in a bash wrapper and run zsh just for the scripts
Thanks!