I would like to create completion rule for comma-separated parameters list. E.g. I have command which receives list of server names:

myscript -s name1,name2,name3

At this moment I've managed to write following completion:

_myscript () {
  local cur prev opts

  _get_comp_words_by_ref cur prev


  servers='name1 name2 name3'

  if [[ ${cur} == -* ]] ; then
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    case "${prev}" in
        if [[ "$cur" == *,* ]]; then
          local realcur prefix
          COMPREPLY=( $(compgen -W "${servers}" -P "${prefix}," -- ${realcur}) )
          COMPREPLY=( $(compgen -W "${servers}" -- ${cur}) )
        # do nothing

But it has at least 2 problems:

  1. Suggestions for current value includes all previous values in their prefix.
  2. It doesn't consider duplicate values.

What is the best practices for such cases? Maybe bash-completions has some bundled functions for csv-lists?

  • 3
    What might help is that you can split comma separated values into an iterable list like this: IFS=, LIST=("$VARIABLE") where $VARIABLE contains your comma separated values. – Michael Ehrenreich Apr 21 '14 at 14:56
  • 2
    Nice idea @MichaelEhrenreich, but you mustn't quote the $VARIABLE, otherwise word breaking doesn't happen. just use IFS=, LIST=($VARIABLE). – Guss Jun 5 '14 at 6:06

There's basically no way to fix the problems you describe, because bash uses the values in COMPREPLY directly in the display and then to replace the user's text - while to get what you want, you'd need to first generate the possible completions (just the additional server names, without the prefix) for bash to display, then when bash is about to replace the user text with the longest non-conflicting string, you'd need it to call your script again to generate the text with the prefix - and bash has no facility for that.

The best that I could come up with is to have the COMPREPLY be generated with only the first word having the entire prefix (COMPREPLY=( "${prefix},"$(compgen -W "${servers[@]}" -- ${realcur}) )), so that if there is only one possible completion, it completes automatically correctly, while if there is more than one possible completion, then bash will not remove what was typed so far (because the first word in COMPREPLY has the entire prefix and thus matches the currently typed text and will be selected by bash to replace the user's text) and will display the options without the prefix - except for that one word that already contains the prefix, so the output will look like this:

$ command -s banana,a
ananas     apricot    banana,apple

"apple" as sorted last in the completion options because it carries the prefix which starts with "b" - very confusing. So I don't recommend doing that.

Regarding the duplicates - in order to not show duplicates, you just need to break $prefix into its part (easy: IFS="," prefix_parts=($prefix)) and then iterate over them and only leave in $servers the names that are not already listed. Its tedious to type, so I won't show it here, but relatively trivial so I'm sure you can manage :-).

To summarize, I don't think you should use comma separated values for input options, at least if you expect bash to help you with completion.

You can support an options format that like this: command -s <server> [<server> [..]] and then for completion of entries other than the one immediately after the -s option, just scan back through the $COMP_WORDS array from $COMP_CWORD until you find an option (string that matches -*) and if its "-s" then you need to do the server name completion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.