A Completion Function
Let us refine the outline of the function from the previous section:
- The word that is to be completed is the value of the array COMP_WORDS at index COMP_CWORD, i.e. "${COMP_WORDS[COMP_CWORD]}".
- Since the list of valid parameters to pulseaudio-tcp is well known, we can simply provide the function with a hardcoded list of valid options and valid command verbs. It makes sense to define those two as separate lists, because special restrictions apply for command verbs – only one may be given on a complete command line.
- To determine which words can potentially be offered to the user as possible completions (not yet regarding the word that is currently being edited, but already regarding the other, complete words that we know from COMP_WORDS), the following rules will be applied:
- If any word already present in the command line (i.e. all already completed words from COMP_WORDS) is a command verb, no command verb will be considered a potential completion.
- No completion will be offered that would result in a word that is already present on the command line, i.e. all words from COMP_WORDS will be filtered out from the list of potential completions.
- compgen will be used to determine the list of effective completions from the list of possible completions from the previous step. The output of compgen will be stored to the array COMPREPLY using the built-in command mapfile.
- When done, the function returns successfully.
Note: See below for a walk-through of the completion function with lots of comments on individual aspects of the code. Click here for the function’s source code without comments.
_pulseaudio_tcp_completions() {
For starters, it is good practice to declare variables as local that will not be used outside of the function:
local \
command_list \
command_pattern \
commands \
cur \
option_list \
options \
word
Step 1: Determine the (possibly incomplete) word in the command line that is currently subject to completion. By mere convention, this variable is often named „cur“:
cur=${COMP_WORDS[COMP_CWORD]}
Step 2: Assign lists of options and command verbs supported by the command pulseaudio-tcp:
options=( "--debug" "--help" "--nogui" )
commands=( "start" "stop" "status" "setup" "restart" )
Step 3: Filter out unwanted elements from these lists of potential completions.
Step 3, rule a: To avoid offering words that already are present in the command line as completions, delete all words in COMP_WORDS (excluding the word that is currently being edited) from the arrays of options and command verbs:
for word in "${COMP_WORDS[@]}" ; do
[[ $word = "$cur" ]] && continue
options=("${options[@]/$word}")
commands=("${commands[@]/$word}")
done
For later use with compgen, format the list of remaining options as a whitespace-separated list:
option_list="${options[*]}"
Step 3, rule b: If a command verb already is present as a word on the command line, do not offer any command verbs as potential completions.
Turn the list of command verbs into a pattern that can be used as a regular expression (command1|command2|…). This is done as follows: Using printf -v VARIABLE_NAME, join the array values with the pipe character | into a new variable command_pattern, then strip the last | and surround the result with parentheses to form a pattern of the form (value1|value2|…):
printf -v command_pattern "%s|" "${commands[@]}"
command_pattern="(${command_pattern%?})"
Match all present words against this pattern and determine the whitespace-separated list of command verbs:
if [[ ${COMP_WORDS[*]} =~ $command_pattern ]] ; then
command_list=""
else
command_list="${commands[*]}"
fi
Step 4: Use compgen to determine the effective list of possible completions and store the result to the array COMPREPLY.
Instead of assigning to the array directly (COMPGEN=…whatever…) we use the built-in command mapfile which expects a series of values, one per line, on standard input and assigns them to a given array. Note that the -t option to mapfile removes the newline characters from the input before assigning the array values. Also note that the string that is currently being edited ("$cur") is a non-option parameter to compgen, but it might start with a --; to avoid syntax errors with compgen, it has to be specifically marked as non-option using --:
mapfile -t COMPREPLY < <(compgen -W "$option_list $command_list" -- "$cur")
Step 5: The function is done and returns successfully.
return 0
}
Once defined, the function can be assigned as completion logic:
complete -F _pulseaudio_tcp_completions pulseaudio-tcp
Again, click here for the code without comments
- Bourne to Bourne Again Shell Forward Compatibility
- Print XDG Desktop Definition for Application
- Using sed or awk to ensure a specific last Line in a Text
- Make a Bourne Again Shell Script Log its Output to a File
- Maintaining Multi-Line „stat“ Formats using Bourne Again Shell
- Print all indented Lines following a non-indented Line
- An Introduction to Programmable Completion in Bash