Implementing simple bash tab completion for guestfish

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.

1 Comment

Filed under Uncategorized

One response to “Implementing simple bash tab completion for guestfish

  1. Pingback: guestfish hits 300 commands « Richard WM Jones

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.