Bash Completion
This week, I’ve been exploring how tab completion works in Bash. I knew that it was possible to generate bash completions for command line utilities, but I never knew how it worked.
So I cracked open the source code for click
, a python framework for command line applications that had shell completion as a feature. Helpfully, the shell completions were contained in shell_completion.py
.The way that click
does shell completions is interesting, it uses the application itself to provide completions. I may revisit how this works in the future.
The most helpful line was one that looked like this:
complete -o nosort -F complete_function program_name
With both precise completion_function
and program_name
determined by the installation of the click
application.
complete
is a bash builtin, but help complete
provides little detail as to how it works. In particular, there’s no mention of the -F
flag that click
uses.
Info to the Rescue
While help
proved to be unhelpful,
info
a tool I knew about,
but hadn’t previously explored,
had exactly what I needed.
Section 8.6 Programmable Completion and Section 8.7 Programmable Completion Builtins proved to share exactly what I wanted to know.I’m linking to the web versions, but this information is available through info bash
on the command line.
Let’s walk through our line of bash from before:
complete program_name
complete
works by registering completions for a particular name
, in most cases this is the builtin or program that we want completions for.
Once those completions are registered, the particular method defined will work every time completion is invoked, typically by the the tab
key.
-o nosort
-o
sets a comp-option
with specifies extra behavior for complete
.
In this case, -o nosort
tells complete
not to sort the completions array that it receives.
In this case, it allows click
to precisely control the order of completions that it shows.
-F complete_function
This is most interesting part of complete
’s behavior.
-F
specifies a bash function that will be invoked when completions are asked for on the registered name.
This function receives three arguments from complete
:
-
$1
is set to the the name of command which is receiving completions. -
$2
is set to the word being completed. -
$3
is set to the word preceding the word being completed.
$3
allows for the invoked function to handle nested commands, such as git
’s branch
which will complete on available branches for the repo.
When the function finishes, the completions are retrieved from the value of the COMPREPLY
array variable.
Putting it together
I now had a mental model of how the completions worked, but I wanted to solidify my knowledge by creating a minimal example.
My name
d command will be my_echo
, a shell wrapper around echo
.
#!/usr/bin/env bash
echo $1
With that in mind, I wrote the following bash:
_my_echo_complete() {
local options=("hello" "hear" "world" "today")
for option in ${options[*]};
do
COMPREPLY+=($(echo "$option" | grep $2));
done
}
_my_echo_setup() {
complete -F _my_echo_complete my_echo
}
_my_echo_setup
I hadn’t worked with bash arrays before, so it took some experimentation to learn about ()
to create an array.
Note that the parenthesis are used both for options
and next to the +=
on COMPREPLY
.
The [*]
operator inside ${options[*]}
unpacks the array so that for
can iterate through it.
Once I wrote this bash, I added it to a file called complete_function
which I then sourced:
$ source complete_function
This version still has issues (hitting tab with no input makes grep throw error once for every item in the array), but it’s a working version of tab completion for a program I “wrote”.
I look forward to trying to add tab completion in bash to future tools.Special thanks to Jeff and Kathrin who joined me in my exploration of Bash completion.