Most people are familiar with the default Bash completion functionality. You start typing a command, hit Tab, and the command gets automatically completed. If there are multiple commands that match what you typed so far, you can hit Tab two times, and Bash displays a list of all possible completions. The same works for variables and filenames.

However, many people don’t know that Bash supports a much more advanced type of completion, called programmable completion. This allows to define command-specific completion logic, which enables users to auto-complete sub-commands, options, and other arguments of commands in a context-specific manner. Just imagine typing cmd -[tab][tab] and then see a list of all applicable options for this command.

Unfortunately, by default, programmable Bash completion doesn’t work without hassle on macOS. However, these hurdles can be easily overcome. This article explains how to do this, and how you can take full advantage of programmable Bash completion on macOS.

Default Completion

The default Bash completion functionality works in basically every version of Bash. It is briefly described here (on the top). In essence, it allows to auto-complete command names, variables, and filenames, as shown here:

$ ec[tab] # Completes to echo

$ echo $PA[tab] # Completes to echo $PATH

$ cat myfile[tab] # Completes to cat myfile.txt

If there are multiple possible completions for what you typed so far, you can hit Tab two times, and a list with all the possible completions is displayed:

$ e[tab][tab] # Lists all commands starting with "e"

$ echo $P[tab][tab] # Lists all variables starting with "P"

$ cat my[tab][tab] # Lists all files starting with "my"

Programmable Completion

Programmable completion has been introduced as a new feature in Bash version 2.04 (dating from around the year 2000). It is described in detail in the Programmable Completion section of the Bash documentation. As its name says, it is a programmable type of completion, which means that it allows to create completion logic that is tailored to individual commands.

For example, it allows to do things like this:

$ cmd [tab][tab] # Lists all sub-commands of "cmd"

$ cmd --[tab][tab] # Lists all options applicable to "cmd"

$ cmd sub --[tab][tab] # Lists all options applicable to "sub"

$ cmd sub [tab][tab] # Lists all arguments applicable to "sub"

$ cmd su[tab] # Completes to cmd sub

$ cmd --opt[tab] # Completes to cmd --option

As you can see, the completions are not generic, such as the command names, filenames, and variables of default Bash completion, but specific to the command that is being typed ( cmd in above example).

So how can Bash know what completions are applicable in each situation? The answer is with a completion specification that has to be defined for each command. The next subsection elaborates on this further.

Completion Specifications

A completion specification (“compspec”) defines the applicable completions for the argument tokens of a command. These argument tokens may be sub-commands, options, or other values, including context-specific values. The completion specification is responsible for inspecting the current state of the command line and returning a set of possible completions.

If a command has a completion specification, and the user hits Tab in an argument token of this command, then Bash invokes the completion specification and displays the returned completions to the user. The meaning of these completions (e.g. whether they are sub-commands or options) is transparent to Bash, and only known to the completion specification and the command itself.

A completion specification is created and tied to a command with the complete builtin. The complete builtin is the main facility for the programmable completion feature of Bash. There are two auxiliary builtins called compgen and compopt that help defining completion specifications. These three builtins together are described in the Programmable Completion Builtins section of the Bash manual.

Roughly, this is how the complete builtin is used to create a completion specification and tie it to a command:

$ complete [compspec] [cmd]

Now, if you type cmd [tab] , Bash invokes the completion specification compspec and displays the returned completions to you (which in this case might be sub-commands, filenames, or other values).

I will not describe in this article how to create a completion specification, but the main information for this can be found in the documentation of the complete builtin. And, even much better for starting, there is a very good tutorial called Creating a Bash Completion Script.

In practice, complete is rarely executed on the command line, but included in a so-called completion script. A completion script is basically an ordinary Bash script that includes one or more complete statements for a specific command. Thus, if you source a completion script, the complete commands are executed in the current shell, and completion gets activated for the corresponding command.

In the Real World

The big question now is, where do the completion scripts for all the commands come from? The answer is, they are typically provided by the creators of the commands.

For example, Git provides a Bash completion script for its git command-line tool. It is called git-completion.bash . If you look at this script, you see that it defines many functions and at the end it calls the complete builtin. The functions define the actual completion specification and they are referenced by the complete statement. You can also see that the script uses the compgen and compopt builtins. This is how most Bash completion scripts look like.

You can download this script and source it in your shell:

$ source git-completion.bash

And immediately after that you should have amazing auto-complete functionality for git . This looks like the following:

As you can see, the completion functionality provided by the Git completion script can be pretty useful. It can save you not only from typing, but also from hitting the documentation to look up the names of sub-commands or options.

Note how hitting Tab in the git add context shows a list of untracked files, but not tracked files or files that are in .gitignore . Or how hitting Tab in the git remote remove context shows a list of the existing remotes as possible completions. These are examples of context-specific completions that make programmable completion so powerful. Programmable completion logic can be arbitrarily complex.

Git is only one of many command-line tools that provide Bash completion scripts. For example, docker provides a Bash completion script that can be found here. Or kubectl also has a Bash completion script, and it even allows to directly generate it with the kubectl command: kubectl completion bash .

Some Complications

You might think that all you have to do is to source these completions scripts in your .bashrc file and you’re able to use the completions for these commands. Unfortunately, it’s not as easy, as we run now into some complications that we first need to sort out. In particular, we need to discuss two points:

Many completion scripts don’t work with the default version of Bash on macOS Many completion scripts depend on a third-party project called bash-completion

The Git completion script from above is an exception in this regard. It works out-of-the-box on macOS. However, if you try to source the docker or kubectl completion scripts, you will have no luck. You will at least get a command not found error, and by no means will you get any useful completion functionality.

Both of these points will be discussed in the following two sections. They can be seen as small hurdles, but it’s easy to overcome them. Once this is done, I will present the best way to take full advantage of programming completion functionality on macOS at the end of this article.

What’s the Problem with macOS?

Apple includes a completely outdated version of Bash in macOS. To be precise it is GNU Bash version 3.2, which dates from 2007. You can see this is if you execute the following command:

$ bash --version

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)

Copyright (C) 2007 Free Software Foundation, Inc.

Apple doesn’t want to include a newer version of Bash, because GNU Bash 4+ (successors of 3.2) use the GPLv3 license, which conflicts with Apple’s licensing policies.

The problem is that the programmable completion features of Bash have evolved since version 3.2, and today many completion scripts make use of new features that are not supported by Bash 3.2. This means that these completion script won’t work with Bash 3.2. For example, the completion scripts for docker and kubectl don’t work properly with Bash 3.2.

That’s of course a problem. And the best way to have a hassle-free experience with programmable completion is to upgrade the default shell of your Mac. It’s actually pretty easy, and I wrote an entire article about it called Upgrading Bash on macOS.

I highly recommend to upgrade the default shell of your Mac to a newer version of Bash before proceeding with this article. Only this ensures that third-party completion scripts will work as expected. At the time of this writing, the latest version is GNU Bash 5.0.

Now, let’s discuss, the second important point regarding programmable completion in practice.

The bash-completion Project

The bash-completion project is an independent project that has existed for almost as long as programmable Bash completion itself (at least since 2003). It is independent from Bash, but has become something like a standard. It is maintained here on GitHub:

But what does it do? Well, it actually does two things.

First, it provides a script called bash_completion . You can find it here on GitHub. It is a Bash script that, among some other things, provides convenience functions for other completion scripts.

Second, it provides a set of completion scripts in the completions directory for commonly used tools like ssh , wget , curl , gzip , and many others. You can see the full list of completion scripts here on GitHub. These completion scripts typically use functions that are defined in the bash_completion script.

Actually, the bash_completion script also sources all the completion scripts in the completions directory. This means, if you install the bash-completion project on your system, and source the bash_completion script (for example, in your .bashrc file), then you immediately enable all the completion scripts in the completions directory.

As mentioned, bash-completion became like a standard. That means that today not only the project’s own completion scripts use the functions defined in the bash_completion script, but also third-party completion scripts.

This means that these third-party completion scripts depend on bash-completion. In other words, sourcing a third-party completion script that depends on bash-completion only works if bash_completion has been sourced previously. Otherwise, you will get a command not found error, because the script invokes bash_completion functions that are unknown to Bash.

This is the case, for example, for the docker and kubectl completion scripts that were mentioned above. They invoke a couple of functions that are defined in the bash_completion script. Thus, these completion scripts depend on bash-completion.

You can look at bash-completion as the “operating system” for programmable completion in Bash, and you should have it on your machine before seriously attempting to use programmable completion in general.

The next section explains how to install bash-completion. The subsequent section explains how to best use completion scripts of third-party commands so that they work properly with bash-completion.

Installing bash-completion

As you can see from the README of the bash-completion GitHub repository, bash-completion can be installed via various package managers. Among them is Homebrew, which I highly recommend to use for installing software on macOS in general.

Installing bash-completion with Homebrew is very easy:

$ brew install bash-completion@2

Note: the @2 token indicates the major version 2 of bash-completion (the latest version, at the time of this writing, is 2.8) which must be used for Bash 4.1 and higher. If you (hopefully) upgraded the default shell on your Mac to the latest version of Bash, then this is the right version to use. If you still use Bash 4.0 or lower, you have to use version 1 of bash-completion ( brew install bash-completion ), however, remember that in this case many completion scripts won’t work on your system!

To complete installation you need to take some additional steps. Note that the output of the brew install command includes the following:

The first line sources the bash_completion.sh script, which in turn sources the bash_completion script that was mentioned in the last section (the script that provides all the functionality provided by bash-completion).

You need to add this line to your .bashrc or .bash_profile file to actually use bash-completion. If you don’t do it, it is as if you hadn’t installed bash-completion at all.

The second line in the above output sets the BASH_COMPLETION_COMPAT_DIR environment variable to /usr/local/etc/bash_completion.d . This allows you to continue using a legacy convention of the Homebrew formula for version 1 of bash-completion.

The convention is that the bash_completion script sources all completion scripts in the /usr/local/etc/bash_completion.d directory. Consequently, many Homebrew formulas simply drop their completion scripts into this directory and assume that they will be sourced by bash_completion (if bash-completion has also been installed with Homebrew). Formulas doing this, typically indicate this fact with an output of the following form:

This is very practical, as completion scripts of new Homebrew formulas start working immediately, without the user having to put anything in their .bashrc or .bash_profile files. But at some point in version 2 of bash-completion this default behaviour was dropped for certain reasons (here is the pull request that removed this feature). However, luckily, there is now the BASH_COMPLETION_COMPAT_DIR variable, which allows you to re-enable this feature if you set it to /usr/local/etc/bash_completion.d , as indicated in the output of brew install above.

If you do so, just note that the second line must come before the first line in your .bashrc file (you must set the BASH_COMPLETION_COMPAT_DIR variable before sourcing the bash_completion script, otherwise it will have no effect). So, you should add the following to your .bashrc file:

export BASH_COMPLETION_COMPAT_DIR="/usr/local/etc/bash_completion.d"

[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"

Let’s have a look what the brew install command actually installed on your system. The base directory for the bash-completion installation is /usr/local/share/bash-completion (assuming that your Homebrew prefix is /usr/local which you can find out with brew --prefix ).

The content of the /usr/local/share/bash-completion directory should contain the following:

bash_completion (file)

(file) completions (directory)

The first item is the bash_completion script mentioned above. The second item is the directory containing all the completion scripts provided by bash-completion for commonly used commands, like ssh , wget , curl , and many others.

Since bash_completion sources the completion scripts in the completions directory, these completion functionalities should already work now. You can test it, for example, with wget by typing wget --f[tab][tab] , which should display a list of all the wget options that start with f .

Because you added a code snippet that sources the bash_completion script to your .bashrc file, it will be sourced in every shell that you launch. This means that the functions defined in bash_completion will now be available to any completion scripts of any tools that you install. The next section explains the best way to get and manage such completion scripts.

Using Completion Scripts

Once bash-completion is installed, using third-party completion scripts that depend on bash-completion is easy. There are actually multiple ways to do it, and three of them are explained in the following subsections in increasing order of user-friendliness.

Simply Source It

Let’s take the completion scripts of the kubectl command as an example. This completion script can be conveniently generated with the kubectl command itself by running kubectl completion bash .

In the previous section I claimed that after installing bash-completion, your system is set up so that third-party completion scripts that depend on bash-completion can be successfully used. If this is true, then sourcing the output of kubectl completion bash should now enable the completion for kubectl . Let’s test it:

$ source <(kubectl completion bash)

Now try to auto-complete kubectl commands:

$ kubectl [tab][tab] # Lists sub-commands of "kubectl"

$ kubectl -[tab][tab] # Lists applicable options for "kubectl"

$ kubectl get -[tab][tab] # Lists applicable options for "get"

It works!

And you can now do the same with any completion script. Just source it (preferably, add a source command to your .bashrc ) and completion for this command should start working.

However, if you installed bash-completion with Homebrew, and if you also set the BASH_COMPLETION_COMPAT_DIR variable, there’s an even simpler way to use third-party completion scripts, as explained in the next section.

Simply Drop It

As we have seen, the bash_completion script sources completion scripts in a couple of locations. For one, this is the completions directory within the bash-completion project (containing completion scripts for basic commands). Furthermore, if you set the BASH_COMPLETION_COMPAT_DIR variable, this also includes the directory that you assign to this variable.

If, as recommended, you set BASH_COMPLETION_COMPAT_DIR to /usr/local/etc/bash_completion.d , then bash_completion will source all the completion scripts in this directory (no matter how they got there). So, you can just drop any completion script into this directory, and it starts working as soon as the bash_completion script gets sourced (which happens when your .bashrc file is sourced).

This means that you can make completion for a command work without having to add anything to your .bashrc file!

Let’s test it by dropping the kubectl completion script into this directory, instead of sourcing it in the .bashrc file:

$ kubectl completion bash >/usr/local/etc/bash_completion.d/kubectl

Note that the name of the file in the target directory doesn’t matter.

Now, restart your shell (or source your .bashrc file), and kubectl completion should be working!

That’s pretty easy now, but you still have to manually put the completion script into the /usr/local/etc/bash_completion.d directory. As we have already seen, many Homebrew formulas put their completion scripts into this directory as a convention, which brings us to the most convenient way of managing completion scripts.

Simply Use Homebrew

Since Homebrew formulas by convention put the completion scripts for their commands into /usr/local/etc/bash_completion.d , you’re even freed of the task to manually move the completion scripts there.

This means, if you install such a Homebrew formula, completion starts working immediately (that is, after restarting your shell or sourcing your .bashrc file), without any further actions from your side!

But remember that this only works if you set the BASH_COMPLETION_COMPAT_DIR variable in your .bashrc file, as indicated in the output of the brew install bash-completion@2 command.

Summary

In this article we covered quite some ground. Programmable completion is a Bash feature that allows to define command-specific auto-completions. This works by completion specifications that are tied to commands by the means of the complete builtin. In practice, completion support is typically provided in the form of completion scripts by the command creators.

macOS includes an old version of Bash that doesn’t support many of the programmable completion features that are used by modern completion scripts. Therefore, it is crucial that you install a newer version of Bash on your Mac before working with programmable completion.

Furthermore, many completion script depend on the bash-completion project, which you thus have to install. The best way to install bash-completion is with Homebrew. This allows to simply drop third-party completion scripts into the /usr/local/etc/bash_completion.d folder where they are picked up by bash-completion and start working automatically.

Homebrew formulas by default put the completion scripts for their commands into /usr/local/etc/bash_completion.d , which means that for these commands, completion starts working immediately, if you have also installed bash-completion with Homebrew.

References

Programmable Completion

GNU Bash

bash-completion

Command-Specific Completions

Appendix

Enabling and Disabling Programmable Completion

Programmable completion is enabled by default in Bash, and it can be explicitly turned on and off with the following commands:

shopt -s progcomp # Enable programmable completion

$ shopt -u progcomp # Disable programmable completion # Enable programmable completion# Disable programmable completion

If you disable it, then all completion specifications are ignored, but the default Bash completion (as described above) still remains active.