How to Write Bash Completion
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