Faster workflow with bash completion scripts

Dec 07, 2018

Open a bash terminal, type cd then press the tab-key twice. A list of directories appears. Type the first letter of one of them then hit tab. The name gets completed.

This is called auto-completion and it is the most important feature of your shell. It can be used for discovery (discover the folders you can go to) and for speed (auto-complete the folder's name). It saves keystrokes.

Auto-completion is not limited to bash's builtin functions. You can define rules to complete your own scripts. And you can also redefine rules for builtin functions.

This article gives an overview of how to do so, either in bash or in the scripting language of your choice. We end with links to step-by-step tutorials.

Bash completion overview

The complete command defines the completion candidates for a command.

Dynamic completion is done through a bash function provided to complete -F: when you hit tab, bash will call a given function responsible to generate completion candidates, then print those candidates. When there's only one candidate, bash completes what you typed.

Here's a minimal working example that always returns two candidates. Write the following snippet inside a new file named cd_completions.sh:

cd_completions.sh
#/usr/bin/env bash
_cd_completions()
{
  COMPREPLY=(candidate_1 candidate_2)
}

# Register this function
complete -F _cd_completions cd

And tell bash to parse the file with the following command:

source cd_completions.sh

Then type cd followed with a space and hit tab twice. You should see candidate1 and candidate2 instead of the usual directory listing. Don't worry everything will be back to normal if you restart your terminal.

To know which candidates it should generate, the function can access the following variables:

  • COMP_WORDS: an array of all the words typed after the name of the program the compspec belongs to;
  • COMP_CWORD: the index of the word the cursor was when the tab key was pressed;
  • COMP_LINE: the current command line.

Using python (or any language)

You can write rules as complex as you want inside the _cd_completions() function. You can also write those rules in the scripting language of your choice and use the bash function as a proxy. For instance, in the snippet below I forward everything to the cd_completions.py script:

cd_completions.sh
#/usr/bin/env bash
_cd_completions()
{
  COMPREPLY=($(  COMP_WORDS="${COMP_WORDS[*]}" \
                 COMP_CWORD=$COMP_CWORD \
                 COMP_LINE=$COMP_LINE   \
                 python3 cd_completions.py
            ) )
}   

# Register this function with:
complete -F _cd_completions cd

Inside cd_completions.py, I can access the variables like this:

cd_completions.py
import os
cwords = os.environ['COMP_WORDS'].split()
cword = int(os.environ['COMP_CWORD'])
cline = os.environ['COMP_LINE']
# Do stuff
print("candidate1 candidate2")

Installing a completion script

Write a bash script with the completion function and the complete -F command as we did above.

If you want to enable the completion just for you on your machine, all you have to do is add a line in your ~/.bashrc file sourcing the script:

source <path-to-your-script>

If you want to enable the completion for all users, copy the script under /etc/bash_completion.d/ and it will automatically be loaded by Bash.

Using bash completion in zsh

I'm a big fan of zsh, an alternative shell. To use bash completion scripts in zsh, write the following line at the end of your ~/.zshrc file:

~/.zshrc
# Enable bash completion scripts
autoload bashcompinit; bashcompinit

Then load any script using the following command:

source <path-to-your-script>

To install a script permanently, source it at the end of your ~/.zshrc.

Step by step tutorials