My name is Philipp C. Heckel and I write about nerdy things.
This site moved here recently from blog.philippheckel.com!

Snippet 0x0B: Bash completion with sub-commands and dynamic options


Code Snippets, Linux, Scripting

Snippet 0x0B: Bash completion with sub-commands and dynamic options


Every system administrator, most programmers and countless of command line surfing Linux/Mac users use it every day without thinking twice. Hitting the tab key twice, [TAB][TAB], has become the most common thing in the world. Bash completion is the magic behind the tab key. It’s easy to use, but it’s a pain to write. This tiny post demonstrates how to write scripts for bash completion, with sub-commands and dynamic parameters. A working script is embedded in my open source file sync software Syncany.


Content


1. Full code

As always, the full working code (embedded in a my open source file sync software Syncany) is available on GitHub. Feel free to use it, or to leave feedback in the comments.

2. Using sy as an example

Bash completion isn’t complicated, but I don’t think there are many good guides/examples out there. I don’t have the know-how to write a good guide, but I can show you how I did it. The Syncany command sy is very powerful because it has several sub-commands and very dynamic options (similar to git).

2.1. Video example

To make it easier to understand how much logic a bash completion script must actually handle, the following short video shows the different possibilities that the script must be able to handle. Among others, that includes the distinction between long and short options, enums, options that require a file completion, options that trigger other sub-options, sub commands, sub sub commands, dynamic options, and so on:

2.2. Text example

Here’s the video output in text form — in case it gets lost or the video is a bit too fast:

3. Writing a bash completion script

You’ve certainly stumbled across the Programmable Completion guide, and probably also its second page, the Programmable Completion Builtins. While those are good resources, they don’t quite tell you the most important parts.

Let me try to do it better:

A bash completion script is a normal bash script containing one or many calls to the bash builtin command complete. This command declares certain functions to handle completions, e.g. complete -F _sy sy to declare completions for sy in the bash function _sy — as in the Syncany bash completion script.

The script is called whenever you press [TAB][TAB] and it is located at /etc/bash_completion.d/yourscript. When this file is changed, you need to open a new window/bash for the changes to take effect!

The main script input are an array called COMP_WORDS (all “words”) and an int COMP_CWORD (current “word” index). ${COMP_WORDS[COMP_CWORD]} gives you the current “word”, ${COMP_WORDS[COMP_CWORD-1]} the previous “word”. The main script output is an an array called COMPREPLY. It defines what suggestions to output to the user.

The input array COMP_WORDS contains all “words” typed into the console, including options starting with a dash. To distinguish between options and words, you must write your own helper function.

The compgen builtin command can be used to fill the COMPREPLY array from a list of words (comgen -W), or a function (compgen -F), and many others. Note that compgen takes the same options as complete. compgen is usally used like this: COMPREPLY=($(compgen -W "init connect log list ls" -- $cur)) (where $cur is ${COMP_WORDS[COMP_CWORD]}, or the actual current word) to fill COMPREPLY with the options that match. If $cur is “l”, for instance, COMPREPLY will contain “log”, “list” and “ls”.

Another useful builtin command is compopt. It can define the behavior of the completion. For instance, compopt -o nospace tells bash not to input a space after the completion. This is particularly useful for long-options with an equals-sign, e.g. --log-file=[TAB] should not add a space.

4. Example script (for Syncany)

The following is the current bash completion script for Syncany (this version on GitHub). It works very nicely for Syncany. It might evolve in the future. Please check out the current script and go from there.

Let me know if you have any questions or corrections. I’d love to hear your feedback!

A. About this post

I’m trying a new section for my blog. I call it Code Snippets. It’ll be very short, code-focused posts of things I recently discovered or find facinating or helpful. I hope this helps.

2 Comments

  1. Daniel

    Thanks for the post! I was having trouble finding valid examples of bash autocompletion, and this is the best one I’ve found!

    True guru style

    +1


  2. MSG

    Awesome script.. and post. Thanks! A few things I still think could make the script even better. For instance I’d prefer that after sy command TAB .. the options are displayed (the – and the — options). Instead the script currently expects you to type either – or — to give you the available options. It could automatically substitute the – for you since the only options are -* or –* as options. Also if you type the command completely and then TAB.. it does not insert a space after it. It only inserts the space if the completion is done on an incomplete argument.


Leave a comment

I'd very much like to hear what you think of this post. Feel free to leave a comment. I usually respond within a day or two, sometimes even faster. I will not share or publish your e-mail address anywhere.