Moving to zsh, part 5: Completions

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.

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 in zsh 5.3. You can fix this with the command compdef _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.

Published by

ab

Mac Admin, Consultant, and Author

12 thoughts on “Moving to zsh, part 5: Completions”

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

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

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

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

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

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

      2. 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 the compinit is not executed in your zsh configuration files. There are some zstyle 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.

      3. You can also install bash5 and switch to using that when you prefer bash behavior.

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

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

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

Comments are closed.