BASH Autocompletion
A couple of posts back, I showed off some functions to pop up notifications when a host became pingable again or when a port became reachable. Today’s (semi) quick tip is how to use BASH’s autocomplete functionality add hostname autocompletion to those notifications functions.
BASH autocompletion is a system that provides tab completion of command arguments. You’re familiar with it’s default behavior which is to complete filenames and paths.
~ ls enctypt<TAB>
encrypt encrypt-only-sym.rb encrypt-time.rb
encrypt-decrypt.rb encrypt-sym.rb encrypt.rb
~ ls encrypt
You can override this behavior by providing BASH with a list of
possible completions. The list can be a literal list of words, or it
can be a function that looks at the current environment ($PWD, user,
time on day, etc) and generates context aware list.
So, what we want is a way to generate a list of hosts we know
about. And, it just so happens we have such a list lying around. You
know how the first time you SSH to a new server, your prompted to
confirm it’s identity? Well, that confirmation, along with the host’s
name is stored in ~/.ssh/known_hosts
. From that file we can extract
a list which should cover most of the servers we care about.
The simple approach is to build a list when you login. If you Google around you’ll find lots of example scripts for pulling hostnames out of known_hosts, but the most common looks like:
echo `cat ~/.ssh/known_hosts | cut -f 1 -d ' ' | sed -e s/,.*//g | uniq | grep -v "\["`
The command that setups autocompletion is complete
. When giving it a list, pass them in as an augument to the -W
option:
complete -W "$(echo `cat ~/.ssh/known_hosts | cut -f 1 -d ' ' | sed -e s/,.*//g | uniq | grep -v "\["`;)" wait-for-host
I’m capturing the output of the command with $()
and wrapping it in double quotes. The last argument is the name of the command (which can be also be a function or alias) that will use this autocompletion. Now we get this:
~ wait-for-host www<TAB>
www.example.com www1.example.com
~ wait-for-host www
That works fine, but it’s static and applies only to one command. Instead you can create a reusable function.
_known_hosts() {
local know_hosts cur
known_hosts=$(cat ~/.ssh/known_hosts | cut -f 1 -d ' ' | sed -e s/,.*//g | uniq | grep -v "\[")
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "$known_hosts" -- ${cur}) )
return 0
}
BASH auto completion functions are powerful things, but for today
we’re keeping it simple. First we build a list of options as
before and store it in $known_hosts
. Second we get the current word, the
command argument, which is be tab completed. Finally, we pass that
list and that word into compgen
which is BASH’s internal
matcher. compgen
has some powerful features, but in this case it’s
just going to return a list of hosts in $known_hosts
that start with
the word we hit tab on.
Then we tell the completion that we are using a function by giving it
the -f
option, along with the function name, instead of -W
:
complete -F _known_hosts wait-for-host
And you can use the _known_hosts
function for other commands as
well:
complete -F _known_hosts ssh
This is a good exercise in understanding autocompletion, but it’s pretty basic. Fortunately, people have already done all of the hard work in the bash-completion project. This ships by default with many Linux distros. On the Mac:
brew install bash-completion
The add:
if [ -f $(brew --prefix)/etc/bash_completion ]; then
. $(brew --prefix)/etc/bash_completion
fi
This will add context sensitive completion to everything from SSH to
rsync, and give you a much smarter _known_hosts
function you can use
with your own commands.
If you want to learn more, I suggest reading the bash-completion code. It does far more powerful things than I cover here and is a great jumping off point for your own completion functions.
Comments