I implemented simple tab-completion for guestfish commands from the shell. For example when using the guestfish -i option it expands the current list of libvirt guests:
# guestfish -i <tab>
CentOS5x32 F13Rawhidex64 RHEL6beta1x64
Debian5x64 FreeBSD8x64 VSphere
F10x32 PCBSD8x64 Win2003x32
F12x64 RHEL55x64 Windows7x32
F12x64preview RHEL620100329n0x64 Windows7x64
# guestfish --ro -i <tab>
CentOS5x32 FreeBSD8x64 Ubuntu910x64
Debian5x64 PCBSD8x64 VSphere
F10x32 RHEL54Betax64 Win2003x32
F12x64 RHEL55x64 Windows7x32
F12x64preview RHEL620100329n0x64 Windows7x64
F13Rawhidex64 RHEL6beta1x64
But notice above that it’s context sensitive. With the –ro option, it offers two extra guests. Those are the two guests that happen to be up and running: read-only is the only safe form of access permitted for those.
Furthermore, if I omit the -i option completely, instead of guests I see guestfish commands:
# guestfish add-<tab>
add-cdrom add-drive-ro add-drive-with-if
add-drive add-drive-ro-with-if
I can even tab-complete the command line options:
$ guestfish --<tab>
--add --listen --no-sync --verbose
--cmd-help --mount --remote --version
--file --new --ro
--inspector --no-dest-paths --selinux
So how is this done?
We start with an outline bash completion script that basically consists of a function to perform the completion (called _guestfish
) and the complete
command invocation. Simplified, this looks like:
_guestfish ()
{
# ...
}
complete -o default -F _guestfish guestfish
To add completion, you can drop this file into /etc/bash_completion.d/ or you can just source it in your .bashrc file.
The complicated part is the completion function, _guestfish
. This function gets several bash variables, and must set a variable with the return value. The important ones are:
-
COMP_WORDS
— the array of “words” on the current command line
-
COMP_CWORD
— the current word containing the cursor (an index into COMP_WORDS
)
-
COMPREPLY
— this must be set to the array of possible completion words on return
The first part of my _guestfish
function examines each word before the current one to work out the context sensitive bit. We’re looking for the -i or –ro option which could change our behavior. The code looks like this:
_guestfish ()
{
local flag_i=0 flag_ro=0 c=1 word
while [ $c -lt $COMP_CWORD ]; do
word="${COMP_WORDS[c]}"
case "$word" in
-i|--inspector) flag_i=1 ;;
-r|--ro) flag_ro=1 ;;
esac
c=$((++c))
done
The second part of our function generates the completions, either by calling virsh list
or by calling guestfish -h
, to list libvirt guests or guestfish commands respectively:
local cmds doms
word="${COMP_WORDS[COMP_CWORD]}"
case "$word" in
--*) # code to complete --options omitted ;;
*)
if [ "$flag_i" -eq 1 ]; then
doms=$(_guestfish_virsh_list "$flag_ro")
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W "$doms" -- "$word"))
else
cmds=$(guestfish -h| head -n -1 | tail -n +2 | awk '{print $1}')
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W "$cmds" -- "$word"))
fi ;;
esac
}
(_guestfish_virsh_list
is an auxiliary function which is just a wrapper around virsh list
).
The only slightly awkward part of this is the mysterious line:
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W "$words" -- "$word"))
What this does is to use the bash compgen
command to generate completion matches for “$word”, where “$words” are all possible completions. Then this is appended to the COMPREPLY
array.
That’s all I had to do to provide a reasonable first pass at bash command line tab completion for guestfish. You can see the full script here.