Bash Completion

Annika Backstrom
in misc, on 4 June 2009. It is tagged #Scripting, #Bash, #compgen, #complete, #productivity, and #shell.

It would be difficult to not like bash's programmable completion. Tab completion is addictive, and expanding it past files and folders into usernames, hostnames, and, well, anything you can dream up and put in a function, has incredible potential.

It's too bad I've had such a hard time wrapping my head around the programmable completion toolkit, complete and compgen.

Getting There

I have a function that works like cd, but prepends a specific directory. Our web files are stored in /some/dir/webapp, and I want that directory at my fingertips at all times. Here's the function:

wa() { cd /web/pscpages/webapp/$1 ; }

With this function, wa brings me to webapp; wa project1 brings me to webapp/project1; and so on. I just provide the full sub-path from webapp. Ideally, I would be able to tab-complete directories in webapp.

complete can pull a list of possible completions from a number of sources: "actions" (like files, directories, commands, shell keywords), command output, a wordlist separated by some whitespace, or the output of a bash function, to name a few. What you've typed so far (the "current word") will be used to filter all the possible completions returned by that source. Say you've typed "pro" and then hit tab to autocomplete. The returned completions need to match "pro" at the start of the string, meaning you can't match against absolute paths like /some/dir/webapp/project1.

compgen can be used to generate a list of possible completions. Matches will be output one per line, and can be piped around for transformations just like any other shell command.

Between these two tools, we have everything we need to autocomplete paths starting in a certain directory. Here's a compgen that gives us directories matching a specified string:

compgen -d /some/dir/webapp/

Sample output:


We need to trim leading directories so "pro" matches "project1." We should also append / to the pathnames, since we're always matching directories:

compgen -S/ -d /some/dir/webapp/ | cut -b 18-

Playing around with compgen's arguments, we can further filter the completion list by appending to our string, sort of an implied glob. Use /some/dir/webapp/p, and subdirectories starting with "p" will be returned. This is exactly what we want: compgen takes care of all the filtering for us. We have access to a couple special variables to examine the word the user is expanding. For now, it's enough just to grab ${COMP_WORDS[COMP_CWORD]} and append it to our path.

When completions are generated by a function, they're passed back to complete by the $COMPREPLY environment variable. Pulling this all together, we can now create our completion function:

_webapp() {
    local cur
    COMPREPLY=( $( compgen -S/ -d /some/dir/webapp/$cur | cut -b 18- ) )

All that's left is to tell bash to use this function to complete our argument to wa.

complete -o nospace -F _webapp wa

The Fruits

So, that does it. Our original wrapper to cd, combined with our autocomplete functionality, looks like this:

wa() { cd /some/dir/webapp/$1 ; }
_webapp() {
    local cur
    COMPREPLY=( $( compgen -S/ -d /some/dir/webapp/$cur | cut -b 18- ) )
complete -o nospace -F _webapp wa

Voila. Tab completion in a directory that's not $PWD, and it even works with subdirectories. I hope this makes autocompletion a little clearer for others.