Bash Auto-completion

This article will give a short run down of how to implement bash auto-completion for a program or script.

Most of this information was found from these links:

A more comprehensive guide can be found in the bash man pages. For more info look up “Programmable Completion” in the bash man page. This is just something to get started.

For this to work at all the package bash-completion needs to be installed on your system.

When performing auto-completion you can specify to bash what shell function or program you want to handle the logic for the auto-completion. This can be done with -F and -C arguments respectively when calling bash builtin “complete”. In this example we will make a script called “greet”, but it can just as easily be a program that has the ability to read environment variables or a bash funcion.

If you just want auto-completion for a set of words you can add the following to bashrc:

complete -W "foo bar blah" greet

This will tab complete greet with the suggestions foo bar and blah. The same functionality is what is being done manually in the next section.

Enabling and getting started

First put this script on your path somewhere i.e. /usr/local/bin, ~/.local/bin, etc.

#!/usr/bin/env bash

if [[ -n $COMP_LINE ]]; then
    printf "foo\nbar\nblah"
    exit
fi

To enable the auto-completion the following line can be entered but is normally be added to the .bashrc or something that runs at startup:

complete -C greet greet

This implies that greet will handle the auto-completion for itself.

The following is an alternate but is usually what is done. Most of the time it will be a bash function that does this which can be specified like:

complete -F _greet greet

Where _greet is the function to handle auto-completion for the program greet.

To test this out, type out complete -C greet greet, then type greet and then keep hitting Tab. The following output should be shown:

$ complete -C greet greet
$ greet
bar   blah  foo

Nothing to write home about, but a start.

Adding auto-completion logic

When an auto-completion is invoked by hitting “Tab”, bash will set a number of environment variables. Namely, COMP_LINE, COMP_POINT, COMP_KEY, and COMP_TYPE. The one we are interested in today is COMP_LINE. This contains the current command line. For example, when typing greet and hitting Tab, COMP_LINE = “greet” Now that we have access to the current command line we can do some extra things.

Currently, all the above script does (it has no internal logic yet) is check COMP_LINE on startup. If it is non-zero print a newline seperated set of words to suggest for completion. This is how bash completion expects suggestions to be given. Also from the bash man page: “When the function or command is invoked, the first argument ($1) is the name of the command whose arguments are being completed, the second argument ($2) is the word being completed, and the third argument ($3) is the word preceding the word being completed on the current command line.”

However, we can add actual completion like so:

#!/usr/bin/env bash

# cmd = "Hello"
# ${VAR:START_INDEX:FINAL_INDEX}
# ${cmd:0:3} = "Hel"

# ${#VAR} prints out the length of VAR

# ${VAR,,} will make the entire word in VAR lowercase

# Store first argument and remove it from arguments list
# In completion this is the name of the program i.e. greet
declare CMD="$1"; shift
# Declare array containing all commands for greet
declare -a COMMANDS=(foo bar blah)

if [[ -n $COMP_LINE ]]; then
    # For each cmd in COMMANDS
    # Check if partially entered command matches list of known commands
    # So, loop through each command, print each command to the same length of characters as the entered command and check if it matches, if so return it as a suggestion.
    # Also checks against the lower-cased version of the entered partial command, making it partially case-insensitive.
    # i.e. if user enters "f" or "fO", it will match with "foo" shortened to "f" or "fo" respectively.
    for cmd in ${COMMANDS[@]}; do
        [[ ${cmd:0:${#1}} = ${1,,} ]] && \
            echo "$cmd"
    done
    exit
fi

You should now see that tab completing greet gives the suggestions foo bar and blah. If a partial completions also work, meaning if greet f or greet fO is typed and then tab is hit, it will complete to greet foo.

Running commands

After the completion logic the following can be added:

_foo() {
    echo "Doing foo with $@"    
}

for cmd in ${COMMANDS[@]}; do
    [[ $cmd = $CMD ]] && "_$cmd" "$@" && exit $?
done

This will check the entered command against known commands and then run it, passing it all arguments

Need to work out how to add directory and filename completion after commands have been auto-completed.

Complete script:

#!/usr/bin/env bash

declare CMD="$1"; shift
declare -a COMMANDS=(foo bar blah)

# Completion
if [[ -n $COMP_LINE ]] && [[ $COMP_CWORD -lt "1" ]]; then
    for cmd in ${COMMANDS[@]}; do
        [[ ${cmd:0:${#1}} = ${1,,} ]] && \
            echo "$cmd"
    done
    exit
fi

# Functions
_foo() {
    echo "Doing foo with $@"    
}

# Function calling
for cmd in ${COMMANDS[@]}; do
    [[ $cmd = $CMD ]] && "_$cmd" "$@"
done

COMP_* environment variables

TBD