Bash: completion, introduction
October 26, 2021•782 words
If you have ever used the TAB key in your terminal, you have interacted with your shell's programmable completion facilities.
Sometimes you probably have found existing completions to be lacking sometimes and wondered about how you can add your own. The Bash manual explains this in great detail, but not in a very accessible manner.
The Process
Bash is going through a series of steps to arrive at the list of completion candidates that it is ultimately displaying:
command
--> compspec
comspec actions: -f, -d, -G, -W
--> completion function or command
--> completion filter
--> add prefixes/suffixes
--> present to user
The compspec provides initial matches for a given word and is set up with the complete builtin.
A completion function or command follows a simple protocol: it receives completion context information through the environment and returns the list of completions, one per line, on stdout.
A special case of this is when the completion function is a Bash function: in this case possible completions can be added to the COMPREPLY array.
complete and compgen

Bash comes with two builtin functions that control how completion: complete and compgen.
In theory you only need complete: it is the entry point for the completion framework and tells Bash how to attempt completion for a command.
The compgen builtin is just a convenience for the common case of completion: given a set of available options and a string the user typed, which options start with the string the user typed?
A minimal example
The complete builtin is a real beast, using almost the full English alphabet for all its single-letter options:
complete: complete [-abcdefgjksuv] [-pr] [-DEI] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]
Let's ignore most of this and focus on a minimal example: we have a function that accept's a human description of a date, like next Thursday and we want to complete the options for that function.
Under the hood this is just GNU date:
d() {
LC_TIME=C date --date="$*" +%F
}
And then we want to be able to invoke it like this:
d next Fri
d last Thu
d next month
In the simplest case we just want completion to match the input against a list of words and complete already supports this simple case using the -W option:
complete -W "$(printf "%q\n" {'next ','last '}{Monday,Mon,Tuesday,Tue,Wednesday,Wed,Thursday,Thu,Friday,Fri,Saturday,Sat,Sunday,Sun})" d
The braces generate all possible permutations of the two sets [next, last] and [Monday, Mon, ... Sun], so in essence our wordlist will look like this:
next Monday
next Mon
...
last Sunday
last Sun
Note that we used the %q format specifier to make sure the spaces in our completion entries are properly quoted.
Now typing d <TAB> will cycle through the list and d n<TAB> will only show the next ... entries.
Refactoring into a function
The complete command we've used before is very unwieldy, so let's move completion for d into a separate function. That also allows us to add more logic to it if we want to support more of date's syntax.
_comp_d() {
local wordlist=$(printf "%q\n" {'next ','last '}{Mon{,day},Tue{,sday},Wed{,nesday},Thu{,rsday},Fri{,day},Sat{,urday},Sun{,day}})
local IFS=$'\n' # separate wordlist entries by newline
COMPREPLY=( $(compgen -W "$wordlist" "$2") )
}
Let's unpack this:
- we create our wordlist like before
- we set
IFSto\n, so thatcompgenknows that newlines separate our wordlist entries (since a single entry contains spaces) - tell Bash about completion candidates by setting
COMPREPLY
compgen -W "$wordlist" "$2" takes our wordlist just like before and narrows it down to entries starting with $2, the word under the cursor.
Anatomy of a completion function
A completion function is invoked with three arguments:
$1is the command for which completion is attempted,$2is the word under the cursor,$3is the word preceding it.
Here are some examples (_ indicates the cursor position):
command $1 $2 $3
d next Fr_ d Fr next
d_ d d
d next_ d next d
Additionally Bash sets a bunch of variables starting with COMP. Let's inspect them:
_comp_dump() {
printf "\n"
declare -p ${!COMP*}
}
complete -F _comp_dump dump
$ dump example one two<TAB>
declare -- COMP_CWORD="3"
declare -- COMP_KEY="9"
declare -- COMP_LINE="dump example one two"
declare -- COMP_POINT="20"
declare -- COMP_TYPE="37"
declare -- COMP_WORDBREAKS="
\"'><=;|&(:"
declare -a COMP_WORDS=([0]="dump" [1]="example" [2]="one" [3]="two")
In the next article we'll explore how to make use of this information and how to generate useful completions for common development tools.