An introduction to bash completion: part 2

Posted by Steve on Sat 24 Dec 2005 at 03:00

Previously we showed how to add basic completion to commands, using facilities which were already provided by the bash completion routines. In this second part we'll demonstrate how to add completely new custom completion to commands.

In part one we looked at adding hostname completion to arbitrary commands by executing:

complete -F _known_hosts xvncviewer

This uses the complete command to tell bash that the function _known_hosts should be used to handle the completion of arguments to the xvncviewer.

If we wish to add custom completion to a command we will instead write our own function, and bind that to the command.

A Basic Example

As a basic example we'll first look at adding some simple completions to the binary foo. This hypothetical command takes three arguments:

  • --help
    • Shows the help options for foo, and exits.
  • --version
    • Shows the version of the foo command, and exits.
  • --verbose
    • Runs foo with extra verbosity

To handle these arguments we'll create a new file /etc/bash_completion.d/foo. This file will be automatically sourced (or loaded) when the bash completion code is loaded.

Inside that file save the following text:

_foo() 
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="--help --verbose --version"

    if [[ ${cur} == -* ]] ; then
        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    fi
}
complete -F _foo foo

To test it you can now source the file:

skx@lappy:~$ . /etc/bash_completion.d/foo
skx@lappy:~$ foo --[TAB]
--help     --verbose  --version  

If you experiment you'll see that it successfully completes the arguments as expected. Type "foo --h[TAB]" and the --help argument is completed. Press [TAB] a few times and you'll see all the options. (In this case it doesn't actually matter if you don't have a binary called foo installed upon your system.)

So now that we have something working we should look at how it actually works!

How Completion Works

The previous example showed a simple bash function which was invoked to handle completion for a command.

This function starts out by defining some variables cur being the current word being typed, prev being the previous word typed, and opts which is our list of options to complete.

The option completing is then handled by use of the compgen command via this line:

COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )

What this does is set the value of $COMPREPLY to the output of running the command:

compgen -W "${opts}" -- ${cur}

If you replace these variables with their contents you'll see how that works:

compgen -W "--help --verbose --version" -- "userinput"

This command attempts to return the match of the current word "${cur}" against the list "--help --verbose --version". If you run this command in a shell you'll be able to experiment with it and see how it works:

skx@lappy:~$ compgen -W "--help --verbose --version" -- --
--help
--verbose
--version
skx@lappy:~$ compgen -W "--help --verbose --version" -- --h 
--help

Here you first see what happens if the user enters just "--" - all three options match so they are returned. In the second attempt the user enters --h and this is enough to specify --help unambiguously, so that is returned.

In our function we simply set "COMPREPLY" to this result, and return. This allows bash to replace our current word with the output. COMPREPLY is a special variable which has a particular meaning within bash. Inside completion routines it is used to denote the output of the completion attempt.

From the bash reference manual we can read the description of COMPREPLY:

COMPREPLY

An array variable from which Bash reads the possible completions generated by a shell function invoked by the programmable completion facility

We can also see how we found the current word using the array COMP_WORDS to find both the current and the previous word by looking them up:

COMP_WORDS

An array variable consisting of the individual words in the current command line. This variable is available only in shell functions invoked by the programmable completion facilities.

COMP_CWORD

An index into ${COMP_WORDS} of the word containing the current cursor position. This variable is available only in shell functions invoked by the programmable completion facilities

A Complex Example

Many commands are more complicated to fill out, and have numerous options which depend upon their previous ones.

As a relevant example Xen ships with a command called xm this has some basic options:

  • xm list
    • List all running Xen instances
  • xm create ConfigName
    • Create a new Xen instances using the configuration file in /etc/xen called ConfigName.
  • xm console Name
    • Connect to the console of the running machine named "Name".

In general the command is "xm operation args" where "args" varies depending upon the initial operation selected.

Setting up basic completion of inital operation can be handled in much the same way as our previous example the only difference is that the operations don't start with a "--" prefix. However completing the arguments requires special handling.

If you recall we have access to the previous token upon the command line, and using that we can take different actions for each operation.

The sample code looks like this:

_xm() 
{
    local cur prev opts base
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    #
    #  The basic options we'll complete.
    #
    opts="console create list"


    #
    #  Complete the arguments to some of the basic commands.
    #
    case "${prev}" in
	console)
	    local running=$(for x in `xm list --long | grep \(name | grep -v Domain-0 | awk '{ print $2 }' | tr -d \)`; do echo ${x} ; done )
	    COMPREPLY=( $(compgen -W "${running}" -- ${cur}) )
            return 0
            ;;
        create)
	    local names=$(for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//} ; done )
	    COMPREPLY=( $(compgen -W "${names}" -- ${cur}) )
            return 0
            ;;
        *)
        ;;
    esac

   COMPREPLY=($(compgen -W "${opts}" -- ${cur}))  
   return 0
}
complete -F _xm xm

Here we've setup the initial completion of the operations and then added special handling for the two operations "create" and "console". In both cases we use compgen to complete the input based upon the text that is supplied by the user, compared against a dynamically created list.

For the "console" operation we complete based upon the output of this command:

xm list --long | grep \(name | grep -v Domain-0 | awk '{ print $2 }' | tr -d \)

This gives us a list of the running Xen systems.

For the creation operation we complete based upon the output of this command:

for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//} ; done

This takes a directory listing of the /etc/xen directory and outputs the names of any files ending in .cfg. For example:

skx@lappy:~$ for x in `ls -1 /etc/xen/*.cfg`; do echo ${x/\/etc\/xen\//}; done
etch.cfg
root.cfg
sarge.cfg
steve.cfg
x.cfg
skx@lappy:~$ 
Other Completion

Using the compgen command we've shown how to match user input against particular strings, both by using a fixed set of choices and by using the output of commands.

It is also possible to match directory names, process names, and other things. See the bash manual for a full description by running "man bash".

The final example demonstrates how to complete files and hostnames in response to two initial options:

#
#  Completion for foo:
#
#  foo file [filename]
#  foo hostname [hostname]
#
_foo() 
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts="file hostname"
 
    case "${prev}" in
	file)
	    COMPREPLY=( $(compgen -f ${cur}) )
            return 0
            ;;
        hostname)
	    COMPREPLY=( $(compgen -A hostname ${cur}) )
            return 0
            ;;
        *)
        ;;
    esac

    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
}
complete -F _foo foo

Using these examples you should now be able to create your own custom completion functions. 95% of the time you only need to complete from a set of available options, the other times you'll need to deal with dynamic argument generation much like we did for the xm command.

Breaking the options down into a small collection of pipes and testing them outside the completion environment (just in the shell) is probably the best approach, then once you've got a working command line just paste it into your function.

For reference my xm completion code can be found online.

 

 


Posted by philcore (70.161.xx.xx) on Mon 26 Dec 2005 at 16:49
[ Send Message | View Weblogs ]
Cool!
bash completion is so good right out of the box, I didn't even look for more configurable options.

[ Parent | Reply to this comment ]

Posted by Anonymous (86.128.xx.xx) on Tue 22 Nov 2011 at 07:32
bash-completion doesn't do anything out of the box. You distro will ship bash-completion scripts with many packages by default so you almost never need to add your own unless it's for software or scripts you've written yourself.

[ Parent | Reply to this comment ]

Posted by Anonymous (50.161.xx.xx) on Sun 26 Jan 2014 at 06:01
I think that's what he meant by "out of the box", numbnuts.

[ Parent | Reply to this comment ]

Posted by darren (66.194.xx.xx) on Wed 4 Jan 2006 at 19:49
[ Send Message ]
Terrific article.
Perhaps there's a clean (or easy) way to handle aliases?
Debian-related example in my .bashrc:
alias apt-i='apt-get install'

Bash completion fails to complete "apt-i" with a list of available pkgs.
And, since there's no "_available_pkgs" built-in function, it appears a one-line "complete -F" won't work here. That leaves me with adding my own custom completion and using the COMREPLY function listed in /etc/bash_completion. Easy enough. But, is there an even easier way? Should I create my alias a different way? Or setup a custom completion that uses the built-in as much as possible?

[ Parent | Reply to this comment ]

Posted by jidanni (210.200.xx.xx) on Fri 27 Jan 2006 at 23:02
[ Send Message ]
Also note the aptsh package.

[ Parent | Reply to this comment ]

Posted by Anonymous (158.125.xx.xx) on Sat 14 Jan 2006 at 22:28
Excellent article!

I've often wondered how this works, thanks!

bye just now, best regards,


Matthew T. Atkinson

[ Parent | Reply to this comment ]

Posted by Anonymous (84.249.xx.xx) on Sat 15 Jul 2006 at 17:10
Great article! Thanks.

[ Parent | Reply to this comment ]

Posted by Anonymous (82.75.xx.xx) on Tue 15 May 2007 at 20:30
This should be used as documentation for bash completion. Great guide!

[ Parent | Reply to this comment ]

Posted by Anonymous (66.243.xx.xx) on Fri 22 Jun 2007 at 23:39
Will it be possible to customize bash completion on the first token (i.e. the command token)?

[ Parent | Reply to this comment ]

Posted by Anonymous (71.212.xx.xx) on Mon 18 May 2009 at 23:43
"${COMP_WORDS[0]}"

[ Parent | Reply to this comment ]

Posted by mar (83.208.xx.xx) on Sun 7 Jun 2009 at 10:28
[ Send Message | View Weblogs ]

Great! Thanks for explanation! Even the basic completion based on your examples (completion of arguments and commands regardless of context) is a great help for my scripts!

[ Parent | Reply to this comment ]

Posted by Anonymous (70.140.xx.xx) on Thu 8 Oct 2009 at 14:37
'See the bash manual for a full description by running "man bash".'

There's even more information in "info bash".

[ Parent | Reply to this comment ]

Posted by Anonymous (66.214.xx.xx) on Wed 16 Dec 2009 at 00:53
This was great thanks

[ Parent | Reply to this comment ]

Posted by Anonymous (190.188.xx.xx) on Sat 18 Jun 2011 at 22:46

Can't your first example be rewritten as simple as this?

complete -W "--help --verbose --version" foo

[ Parent | Reply to this comment ]

Posted by Steve (82.41.xx.xx) on Sat 18 Jun 2011 at 22:50
[ Send Message | View Steve's Scratchpad | View Weblogs ]

Yes, although that obviously only works with simple examples - and the approach here demonstrates how you could begin to do context-sensitive completion..

Steve

[ Parent | Reply to this comment ]

Posted by Anonymous (62.1.xx.xx) on Mon 5 Sep 2011 at 07:54
Wow, so easy and cool!

[ Parent | Reply to this comment ]

Posted by Anonymous (188.223.xx.xx) on Thu 15 Dec 2011 at 21:38
Worth noting that you can default to normal completion using -o default:

complete -o default -F _function program

This means you'll get standard completion of filenames and directories, for example.

[ Parent | Reply to this comment ]

Posted by Asenar (81.57.xx.xx) on Fri 16 Mar 2012 at 15:17
[ Send Message ]
Great Article !

I created a script and I use bash_completion, but I cannot figure out how to allow the completion of "--name=value1 --name=value2" (the "=" sign in the middle stop any other completion, and I tried to play with suffixes/prefixes without any success :s

[ Parent | Reply to this comment ]

Posted by Anonymous (116.21.xx.xx) on Fri 22 Mar 2013 at 01:59

case "${prev}" in
foo)
opts="file hostname"
COMPREPLY=( $(compgen -F "${opts}" -- ${cur}) )
;;
...

Wouldn't be better ?

[ Parent | Reply to this comment ]

Sign In

Username:

Password:

[Register|Advanced]

 

Flattr

 

Current Poll

Which init system are you using in Debian?






( 1059 votes ~ 6 comments )

 

 

Related Links