Adding bash completion for your own tools – an example for pss

December 26th, 2013 at 3:49 pm

Modern Bash has a very useful programmatic tab-completion feature. This feature is one of the main reasons that the terminal is still the most convenient interface to interact with a computer, IMHO.

"Programmatic" means that it’s easy to add completion features for your own command-line tools. Yesterday I figured it will be a good exercise to add this for pss to complete the names of file types it can recognize; the list is growing longer with every release, and it would be nice to be able to quickly come up with the right type name without looking in the help all the time (is it --js or --javascript? etc).

So, simple completion is now live in the master branch of pss on Github. It consists of two parts. The first is this bash script (in the tools/ directory):

    local cur_word prev_word type_list

    # COMP_WORDS is an array of words in the current command line.
    # COMP_CWORD is the index of the current word (the one the cursor is
    # in). So COMP_WORDS[COMP_CWORD] is the current word; we also record
    # the previous word here, although this specific script doesn't
    # use it yet.

    # Ask pss to generate a list of types it supports
    type_list=`pss --show-type-list`

    # Only perform completion if the current word starts with a dash ('-'),
    # meaning that the user is trying to complete an option.
    if [[ ${cur_word} == -* ]] ; then
        # COMPREPLY is the array of possible completions, generated with
        # the compgen builtin.
        COMPREPLY=( $(compgen -W "${type_list}" -- ${cur_word}) )
    return 0

# Register _pss_complete to provide completion for the following commands
complete -F _pss_complete pss pssc

It’s commented enough to be self-documenting, I hope. The most useful resource I found online that explains everything in detail is this page from Debian. For a bunch of sample completion scripts in the Debian repository, see this page. And, of course there’s man bash.

You’ll note that above I call pss --show-type-list to produce a list of whitespace-separated words which is then used by Bash’s compgen command to generate completions. This is a new hidden option I added to pss just for this purpose. It’s actually a common pattern in Bash completions: the tool itself knows best, so tools usually have special options the completion script can use to query things before it presents a list to the user.

Lastly, this needs to be integrated into your system. All we really have to do is source this bash script somewhere so it’s part of your shell. There’s already a place for such completion scripts on Ubuntu systems, in /etc/bash_completion.d; all files in that directory are sourced by the main bash completion script. I added a soft link to my script there, and things work great.

Related posts:

  1. irb tab completion
  2. Keeping persistent history in bash
  3. tools for single-person development
  4. conversion tools that don’t scale
  5. Book review: “The 25 Best Time Management Tools & Techniques” by P. Dodd and D. Sundheim

2 Responses to “Adding bash completion for your own tools – an example for pss”

  1. Michael SartainNo Gravatar Says:

    This is great Eli – very useful. I’m addicted and hooking it up to all my utilities…

  2. Bruce DawsonNo Gravatar Says:

    At the risk of heresy let me say that I find the tab-completion in Windows superior in some ways:

    The main one is tab-completion respects globbing. So “*.sln” followed by tab completes to a file that ends in .sln. I use that hundreds of times a day.

    The second one is that Windows tab completion cycles through the choices. I prefer this. Yes, Linux supports this, but…

    The third one is that Windows tab completion lets you cycle backwards through the choices with shift+tab. I can’t get this to work on Linux. It is super useful if you need the third file out of fifty that match the pattern and you accidentally tab to the fourth one. I use this frequently.

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)