Bash has a programmable completion feature that allows the command line to be completed from a partial command with a press of the <Tab> key. This feature is very useful and provided by many software packages.

If you run ls /usr/share/bash-completion/completions, you will find many completion scripts provided by those packages. For example, the completion script provided by package systemd is located at /usr/share/bash-completion/completions/systemctl.

However, sometimes completions are not available due to:

  • Some binary packages don’t come with completion scripts.

  • Programs are compiled from source which doesn’t have a completion script.

  • User-defined functions and aliases.

Fortunately, we can write our own completion scripts for any command we use.

Problem Definition

We illustrate Bash completion by defining the problem. We have a command mycmd, which accepts a single argument from choices (bob, jack, john). We want Bash to auto-complete the argument as we type.

To solve this problem, we need to write a Bash completion script and put it at ~/.bash_completion. The script calls the complete builtin to add a completion.

Depending on your settings, you may need to press <Tab> twice for completion. But in this text we just write one for conciseness.

Solutions

Now we give solutions to the above problem.

Solution 1

Script:

complete -W 'bob jack john' mycmd

Output:

$ mycmd <Tab>
bob   jack  john
$ mycmd j<Tab>
jack  john

This is probably the most basic (but useful) settings we can have. The -W option specifies a list of argument candidates. And the shell is clever enough only giving candidates with matching prefix.

Solution 2

Script:

_mycmd () {
    COMPREPLY=(bob jack john)
}
complete -F _mycmd mycmd

Output:

$ mycmd <Tab>
bob   jack  john
$ mycmd j<Tab>
bob   jack  john

To add more flexibility, we can use a completion function.

The function accepts these inputs:

  • $1: The name of the command whose arguments are being completed.

  • $2: The word being completed.

  • $3: The word preceding the word being completed on the current command line.

And sets the following output:

  • COMPREPLY: An array variable from which Bash reads the possible completions, each element containing one possible completion.

The script above doesn’t really use the inputs. It just demonstrates how to use a completion function. Therefore it’s not as clever as the first solution, which is shown by always returning bob even if the argument starts with a j.

Solution 3

Script:

_mycmd () {
    candidates=(bob jack john)
    COMPREPLY=()
    for candidate in ${candidates[@]}; do
        if [[ "$candidate" == "$2"* ]]; then
            COMPREPLY+=("$candidate")
        fi
    done
}
complete -F _mycmd mycmd

Output:

$ mycmd <Tab>
bob   jack  john
$ mycmd j<Tab>
jack  john

This script uses a function and is more clever than the above one. It proves a function is at least as powerful as a word list. Slight modification will allow suffix matching instead of prefix matching, or both, or regex matching, or whatever.

Reuse Existing Completions

The above solutions work with both functions and aliases. In fact, it doesn’t even require the function or alias to exist because it simply matches what you type in the command line.

However, if we are using alias, then probably we want our alias to have the same completions as the command to which it is aliased. For example, if we set alias mysystemctl systemctl, then we probably want mysystemctl to have the same completions as systemctl.

The script to do this is as follows:

_mysystemctl () {
    . /usr/share/bash-completion/completions/systemctl && _systemctl
}
complete -F _mysystemctl mysystemctl

Test its output:

$ mysystemctl <Tab>
add-requires
add-wants
cancel
...
$ mysystemctl list-<Tab>
list-dependencies
list-jobs
list-sockets
...

Post-Extend Existing Completions

You can even add some post-processing to the COMPREPLY array resulted from the inner function:

_mysystemctl () {
    . /usr/share/bash-completion/completions/systemctl && _systemctl
    for i in ${!COMPREPLY[@]}; do
        COMPREPLY[$i]="${COMPREPLY[$i]}"_suffix
    done
}
complete -F _mysystemctl mysystemctl

Test its output:

$ mysystemctl <Tab>
add-requires_suffix
add-wants_suffix
cancel_suffix
...
$ mysystemctl list-<Tab>
list-dependencies_suffix
list-jobs_suffix
list-sockets_suffix
...

The bash-completion Project

The bash-completion project (Homepage) contains a lot of useful completion scripts from commonly used commands. It also provides helper functions to make it easier to write a custom completion script. For example, this is a solution to the same problem using helper function _init_completion:

_mycmd () {
    local cur prev words cword
    _init_completion || return

    candidates=(bob jack john)
    COMPREPLY=()
    for candidate in ${candidates[@]}; do
        if [[ "$candidate" == "$cur"* ]]; then
            COMPREPLY+=("$candidate")
        fi
    done
}
complete -F _mycmd mycmd

Conclusion

I believe audience as clever as you have got the idea of how to write Bash completions now.