Writing your own autocompletion
When creating a command line tool for use by a small group it’s probably sufficient to print a helpful message when running with the --help
flag. But once you start thinking about redistributing your tool you’ll want to make adoption as easy as possible. An autocompletion script is a good step in that direction. If your tool has a reasonable number of well–named parameters, autocompletion acts as a quicker substitute for referring to a man
page or --help
, and even users completely unfamiliar with the tool can get a good idea of what it’s capable of and how easy it will be to use.
The simplest autocompletion is no autocompletion. As mentioned above, file path completion is the default, and that covers a number of simple commands. But all commands benefit from a --help
flag, and many commands can benefit from common flags such as --verbose
or --quiet
to show more or less output, --color=[auto|always|never]
to set whether the output should be colored, or --config=FILE
to point to a configuration file. Let’s try implementing completion for a fictional “do what I mean” command, dwim
. We start by setting up a completion for a dwim
command which only takes one argument, the static string --help
:
complete -W '--help' dwim
This script is meant to be sourced, which is why it doesn’t have all the From the Terminal to Production bells and whistles.
The complete
command specifies how arguments are to be autocompleted. Running the command above means that the command called dwim
will be autocompleted with the single argument (“word”; -W
) --help
unconditionally. To test it, simply run the above complete
command, type dwim --
and press Tab: the command line will now read dwim --help
. It doesn’t matter that no dwim
command exists – the completion is completely independent of the command.
The completion does not include completion of the
dwim
command itself – if you typedwi
and press Tab it will not complete todwim
(unless, of course, you have installed a program called “dwim”). Completion of the first word is handled by a different mechanism.
Let’s make this more interesting by adding support for --color=[auto|always|never ever]
:
_dwim() {
local before_previous_word color_values completions current_word options \
previous_word
# Create an array containing all the options: "--color=auto",
# "--color=always", "--color=never ever" and "--help"
color_values=('auto' 'always' 'never ever')
mapfile -t options < <(printf -- "--color='%q'\n" "${color_values[@]}")
options+=('--help')
# Save the last three words of the command, up to and including the
# position of the cursor
before_previous_word="${COMP_WORDS[$((COMP_CWORD-2))]}"
previous_word="${COMP_WORDS[$((COMP_CWORD-1))]}"
current_word="${COMP_WORDS[COMP_CWORD]}"
# Generate completion when the cursor is at the end of a partial color
# value, for example `--color=al`
if [[ "$before_previous_word" == '--color' ]] \
&& [[ "$previous_word" == '=' ]]
then
mapfile -t completions < <(
compgen -W "$(printf '%q ' "${color_values[@]}")" -- "$current_word"
)
mapfile -t COMPREPLY < <(printf "%q \n" "${completions[@]}")
return
fi
# Generate completion when the cursor is at the end of `--color=`
if [[ "$previous_word" == '--color' ]] && [[ "$current_word" == '=' ]]
then
mapfile -t COMPREPLY < <(printf "%q \n" "${color_values[@]}")
return
fi
# Generate default completion, suggesting all the options
mapfile -t COMPREPLY < <(compgen -W "${options[*]}" -- "$current_word")
}
This page is a preview of The newline Guide to Bash Scripting