How to create a bash auto complete

Bash auto-complete seems pretty magic. People who use bash extensively (I'm part of them) tend to have a certain love for the [TAB] key! It looked mysterious to me until I implemented one for myself. I'm not proficient at Bash, but basic knowledge of arrays and compgen will make you king of autocomplete.

The compgen magic

This Bash builtin is the base of auto-completion. It takes two arguments: a list of terms, a dictionary, and an input to compare it with. It will return the list of terms in the dictionary which could match with the input. You can use it like this:

$ compgen -W 'init list new compile install lint' -- li
list lint
$ compgen -W 'init list new compile' -- compile
$ compgen -W 'init list new compile' -- lists

The options -W stands for words. You can find the documentation of this builtin and in man bash or here.

Working on a simple example

Let's say we decide to create an auto-complete functionality for goat, your homemade version of git. We would like to complete subcommands like goat log or goat commit. Firstly you'd need to create a script to let Bash know how to perform the completion. You'd write this script in /etc/bash_completion.d/goat.

The content would look like this:

    local cur opts
    opts="log commit push pull clone add"
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
complete -F _goat goat

COMP_WORDS, COMP_CWORD are variables provided by Bash respectively containing an array of each terms in the command line and the index of the word being completed (more documentation on bash internal linked with completion here). The selection of words suggested to the user will be taken from the COMPREPLY variable. Calling the complete builtin will allow Bash to perfom the actual completion. Here's the result:

$ goat [TAB]
log commit push pull clone add
$ goat com[TAB]
$ goat commit
$ goat pu[TAB]
push pull
$ goat ad[TAB] some_file
$ goat add some_file
$ goat log [TAB]
log commit push pull clone add

As you can see in the last attempt to complete the sentence, there is no consideration of the place in the command line you are which would do a pretty bad autocompletion feature. Plus there is no completion of options or sub commands.

Where to go from here

If you want a smarter completion feature, you'll have to:

  1. Suggest based on subcommands and options available to the user. This means you need to create a tree of possibilities: goat push supports --force option but goat pull does not, etc.

  2. Suggest based on the type of argument expected. For instance Git auto completion will suggest branches and remote repository when using git push [TAB].

  3. You can even suggest files based on their types, size, content, etc.

Those tasks might feel simple to achieve, but remember that you also have to deal with Bash language which is not the most elegant one. To give you an idea, docker has a 3k lines script to perform completion.

I hope this post helped you!