0

I created a completion function inspired by How to Create a Custom Bash Completion Script (which has some obvious errors but I corrected these) and in a newer version on GitHub.

bash_completion_test

#!/bin/bash
#
# bash_completion_test
#
# from https://maskedbyte.com/how-to-create-a-custom-bash-completion-script/
#  and https://github.com/SumuduLansakara/custom-bash-completion-script/blob/master/completer.sh
#

echo 'Begin bash_completion_test...'

# test definitions
_args=(add list remove)
list_args=(student teacher)
list_student_args=(age grade name)
list_student_grade_args=(-a -o --another --option)
list_teacher_args=(name)

#
# Generic hierarchical completion
#
function generic_hierarchical_completion_test() {

    local arguments word_list_ptr word DEBUG
    
    log='bash_completion_test.log'
    echo -n '␃' > $log  # clear log file
    
    DEBUG= #true  # set variable for debugging info in log file
    [[ $DEBUG ]] && echo -n > $log
    
    # Array of individual words in the current command line
    # see https://www.gnu.org/software/bash/manual/bash.html#index-COMP_005fWORDS
    [[ $DEBUG ]] && echo "COMP_WORDS=${COMP_WORDS[@]}" >> $log
    # Second word to last word in the current command line
    arguments=("${COMP_WORDS[@]:1}")
    [[ $DEBUG ]] && echo "arguments=${#arguments[@]}:${arguments[@]}" >> $log

    # Remove last empty word from arguments
    [[ "${#COMP_WORDS[@]}" -gt 1 ]] && unset 'arguments[${#arguments[@]}-1]'
    [[ $DEBUG ]] && echo "arguments=${#arguments[@]}:${arguments[@]}" >> $log

    # Create word list pointer variable from arguments while replacing spaces with '_'
    IFS=_
    word_list_ptr="${arguments[*]}_args[*]"
    unset IFS
    [[ $DEBUG ]] && echo "word_list_ptr=$word_list_ptr" >> $log
    [[ $DEBUG ]] && echo "word_list=${!word_list_ptr}" >> $log
    
    # -W <word list from variable indirected by word_list_ptr>
    # COMP_CWORD: An index into ${COMP_WORDS} of the word containing the current cursor position.
    # see https://www.gnu.org/software/bash/manual/bash.html#index-COMP_005fCWORD
    word=${COMP_WORDS[${COMP_CWORD}]}
    [[ $DEBUG ]] && echo "word=$word" >> $log
    COMPREPLY=( $( compgen -W "${!word_list_ptr}" "$word" ) )
}

complete -F generic_hierarchical_completion_test ct

echo 'End bash_completion_test.'

ct

#!/bin/bash

echo ">> $# arguments: $*"

This works well until $ ct list student grade .

If I $ ct list student grade TAB the result is $ ct list student grade -. So far so good.

If I $ ct list student grade -TABTAB the expected result is:

$ ct list student grade -
--another  --option   -a         -o

But now it's becoming @§$%&!

If I $ ct list student grade --TAB the result is $ ct list student grade -, i.e. the trailing hyphen is removed.

If I $ ct list student grade -aTABTAB it shows all the options correctly but all my aliases in addition (one of them is alias a=alias):

$ ct list student grade -a
--another  --option   -a         -o         ..         a          b          bp         c          df         kc         ll         ls         x

If I $ ct list student grade --aTAB I get an error:

$ ct list student grade --abash: compgen: --: invalid option
compgen: usage: compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word]

How can I handle short '-...' and long '--...' options correctly?

Gerold Broser
  • 14,080
  • 5
  • 48
  • 107
  • `word_list_ptr="${arguments[*]}_args[*]" "${!word_list_ptr}` ugh, this will be getting complicated and harder to debug really really fast. While it's great to do "abstract references", it's really better to write clean, easy to reason code that is easy to write and debug. Consider refactoring that to `case "${arguments[*]}" in "list student grade") complist="complete this"; ;; "list something else") complist="complete that list"; ;;`. `the trailing hyphen is removed.` this is most probably the reason - arguments has `-` from `"${COMP_WORDS[@]:1}"`, so `${!word_list_ptr}` just fails. – KamilCuk Nov 16 '21 at 19:46

1 Answers1

1

How can I handle short '-...' and long '--...' options correctly?

You have to separate compgen options and word.

compgen -W "<words>" -- "$word"

Reference

Guideline 10:

The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the '-' character.

--

A -- signals the end of options and disables further option processing. Any arguments after the -- are treated as filenames and arguments.

Gerold Broser
  • 14,080
  • 5
  • 48
  • 107
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • What does this? The sample at https://www.gnu.org/software/bash/manual/bash.html#A-Programmable-Completion-Example-1 uses this as well, but the text in this and in the previous chapter https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion-Builtins-1 doesn't mention it. – Gerold Broser Nov 16 '21 at 19:58
  • With that completion works, but I have to filter out the options then, otherwise I get errors when TABing further: `$ ct list student grade -a bash: list_student_grade_-a_args[*]: bad substitution` and `$ ct list student grade --another --abash: list_student_grade_--another_args[*]: bad substitution`. – Gerold Broser Nov 16 '21 at 20:03
  • 1
    `compgen -this-is-an-option --this-is-also-an-option -- --this-is-not -also-not-an-option not-an-option`. `--` separates options from non-options arguments. For example, you have a file named `--file`. You can `cat -- --file` or `touch -- --file`. – KamilCuk Nov 16 '21 at 20:09
  • 1
    The reference would be https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html guideline 10 – KamilCuk Nov 16 '21 at 20:10