Kurt McKee

lessons learned in production

Hey there! This article was written in 2009.

It might not have aged well for any number of reasons, so keep that in mind when reading (or clicking outgoing links!).

Bash programmable completion

Posted 26 July 2009 in bash

A long time ago I read a blog entry about a shell called zsh that supported command line completion like I had never seen before. Don't get me wrong, I've been using filename completion for years, but I had never seen a shell perform filename completion across the network over an SSH connection before, or complete a manpage name, or complete a long option to a program (like mplayer's obscure -noflip-hebrew-commas option). The powers of zsh impressed me, but I never got around to switching to it. Years later, now, I've discovered that my current shell, bash, actually does support programmable command line completion, and I've become so impressed that I wanted to share some of the things that I've found.

Directory-only completion

First, you need to know what command to use to actually set up programmable completion in bash. It's called complete, surprise, and I'll give you a simple example of how to use it. Let's say that you've just downloaded that totally rockin' piece of software in a zipfile. It's named "baloo-kit-latest.zip", and when it's extracted it goes into a single directory named "baloo-kit-5.0". Now, bash isn't too bright, so when you try to complete the directory name, bash unhelpfully fails to match only the directory:

$ cd b <tab>  
$ cd baloo-kit- <tab> <tab>  
baloo-kit-5.0/        baloo-kit-latest.zip

Thanks for nothing, bash! But just wait a second, it gets awesome:

$ complete -d cd  
$ cd b <tab>  
$ cd baloo-kit-5.0/

complete's -d option restricts filename completion to directories.

Filtering filenames

Suppose you're dealing with a command that deals with documents with certain extensions, such as unzip. complete has a switch, -X, with which you can specify filtering. The -X switch will exclude whatever you specify, but you can invert the matching (so that instead of excluding you're including) by prefacing the pattern with "!".

$ complete -f -X '!*.zip' unzip  
 $ unzip <tab>  
$ unzip baloo-kit-latest.zip

The -f switch specifies filename completion, and -X excludes everything from the results that doesn't match the pattern specified. Be careful to quote everything properly!

Matching usernames

complete has many built-in completion possibility sources, all of which can be specified using the -A switch. The -f and -d switches are actually convenient shorthand equivalents for -A file and -A directory, respectively. As another simple example, I know that the usermod command deals not with files but users, which is something bash can provide completion for:

$ complete -A user usermod  
$ usermod <tab>  
alpha     bravo     charlie   root

Limitless possibilities

While bash provides a lot of built-in possibility sources, it can't possibly cover everything, which is why complete also allows you to run a command (the -C switch) or a shell function (the -F switch). With these, it's possible to complete hostnames from the SSH known_hosts file, to complete remote pathnames over an SSH connection, to complete git branch and tag names, or anything else. You can find a bunch of smart completions already written over at the Bash-Completion project, and git has a thorough set of completions included in its distribution.

Smart command line completion can make you faster and more effective, so get after it!