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:
- https://www.youtube.com/watch?v=Ih903YwCKTc
- https://opensource.com/article/18/3/creating-bash-completion-script
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