1

I have a program, say foo, like many others that takes command-line arguments of the form:

foo [ options ] command ...

where options are zero or more short (e.g., -s) or long (e.g., --long) style options followed by exactly one command keyword followed by argument(s) for said command.

I want to have a Bash completion function that can complete either long options or a command. Conveniently, foo also takes the --options option that prints its list of options and --commands that prints its list of commands.

I want the function to offer suggestions only for the first non-option word (the command) on the command-line and offer no suggestions for subsequent words upon typing tab.

Using part of this answer, I tried using COMP_CWORD and performing completion only when its value is 1. Here is my completion function so far:

_foo() {
  local cur=${COMP_WORDS[COMP_CWORD]}

  if [[ "$cur" == -* ]]
  then COMPREPLY=($(compgen -W "$(foo --options)" -- $cur))
  else
    if (( COMP_CWORD == 1 ))
    then COMPREPLY=($(compgen -W "$(foo --commands)" -- $cur))
    else COMPREPLY=()
    fi
  fi
}

This almost works. However, if the user gives an option before a command like:

foo --long tab

then hits tab, the shell beeps. I assume it's because COMP_CWORD is 2 at this point because it doesn't distinguish between options and non-options.

OK, so then I though to have the function scan all words before the current word and subtract the number of words starting with - like this:

_foo() {
  local cur=${COMP_WORDS[COMP_CWORD]}

  if [[ "$cur" == -* ]]
  then
    COMPREPLY=($(compgen -W "$(foo --options)" -- $cur))
  else
    local i=$COMP_CWORD
    while (( i > 1 ))
    do
      [[ "${COMP_WORDS[i-1]}" != -* ]] && break
      ((--i))
    done
    if (( i == 1 ))
    then COMPREPLY=($(compgen -W "$(foo --commands)" -- $cur))
    else COMPREPLY=()
    fi
  fi
}

This works, but the problem is if the user gives a short option that takes an argument and provides said argument as the next word like:

foo -s arg tab

then the index will be off by one. The completion function would have to have detailed knowledge of all the options, whether they take arguments, and have to effectively parse all the options to get the index right.

That seems like a lot of work. Is there a simpler way to have my completion function offer suggestions only for the first non-option word? Or just give up and always offer the suggestions?

3
  • 1
    Perhaps by checking if any of the args already given match one of the allowed commands, and if so then don't do any auto-completion? That might work, at the expense of disabling tab completion if an option's argument happens to be the same as one of the valid commands.
    – Wildcard
    Commented Apr 25 at 16:01
  • @Wildcard I think that might work. Let me wait a bit to see whether anybody else comes up with anything else. Commented Apr 25 at 16:32
  • 1
    @Wildcard Nope: nobody came up with anything better. I'd mark your response as the answer if it were an answer rather than a comment. Commented May 5 at 14:20

1 Answer 1

1

The most complete way would be, like you say, to make your completion function have the detailed knowledge of which options take arguments and which don't and do all of that parsing. But that's not very practical.

A much simpler way would be to check if any of the args already given match one of the allowed commands; and if so, then don't do any auto-completion. This should work, at the expense of disabling tab completion if an option's argument happens to be the same as one of the valid commands. But that's quite an edge case so would likely be an acceptable trade-off for the simplicity gained.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .