Over the years I’ve developed a command-line tool I use for routine tasks such
as provisioning my machine, generating project templates and managing secrets.
The tool is written in Ruby and I invoke it with the
Most of what it does is fairly
straightforward. The clever bits are usually delegated to something else. For
zz provision is really just a wrapper that installs and runs Chef,
while passing various options to it.
Recently, I added Bash completion to my tool. I’ve wanted this for a while, but
decided to add it now in preparation for secrets
management. For example, I want to be able to type
zz secret --readamaz<TAB><TAB> and have it complete to
zz secret --read amazon/. Perhaps
<TAB> again will list all secrets under this path, e.g. username,
password, access_key, etc.
In Bash, completion is handled through the
$ type completecomplete is a shell builtin
This command allows you to register a method of completion for a command. For
rgb command might register its known colors:
You could then complete color names:
$ color <TAB><TAB>blue green orange pink purple red yellow$ color p<TAB><TAB>pink purple$ color pi<TAB> # completes to pink
-W switch configures a static list of completions that are printed in
alphabetical order. It’s just one of the many methods of completion.
To see which commands have completion methods, run
complete without arguments:
Here you can see
rbenv support completion. They use the
switch to specify functions to handle their completion, namely
_rbenv. When you complete one of
these commands, their output is context-aware:
$ rbenv install 2.5<TAB><TAB>2.5.0 2.5.0-rc1 2.5.1 2.5.2 2.5.3
rbenv has kindly listed which Ruby 2.5.x versions are available
to install. We could find this out from
rbenv install --list but that’s
inefficient because we’d have to clear our current command then re-type it.
When a function is registered as the method of completion with the
it must comply with an ‘interface’ of sorts. When the function is called, Bash
sets some environment variables to be used by the completion function.
They tell it the contents of the
command-line, the cursor position, etc. For example,
$COMP_LINE contains the
full line that was typed,
$COMP_WORDS is that same line broken into an array
of words and
$COMP_POINT is the cursor’s index position.
In return, the completion function should set
$COMPREPLY to specify which
completions to print for the command.
Everybody loves FizzBuzz, right? Let’s demonstrate Bash completion with a custom function that magically completes the next term in the sequence:
Our command is called
fizzbuzz so we name
our completion function
_fizzbuzz, as per the convention. We first set the
length variable to the number of words on the command-line and
one less, since ‘
fizzbuzz’ itself counts as a word.
We’ve probably all seen FizzBuzz before so let’s skip the modulo logic. The
important part is to set
$COMPREPLY - in this case, to an array of the next
term in the sequence.
Now, if we type
fizzbuzz <TAB><TAB>, Bash completion kicks in and as if by
magic the next term is appended to the current command-line. Our
command doesn’t actually exist but that doesn’t seem to matter!
As you can see, there’s plenty of fun to be had! In part two we’ll implement Bash completion for my automation tool and see how it works in practice.
© 2019 Chris Patuzzo