Bash – How to use bash’s complete or compgen -C (command) option

autocompletebash

When creating shell completion functionality in bash, it seems like the -C option would be powerful and common. It's defined like this:

-C command

command is executed in a subshell environment, and its
output is used as the possible completions.

That seems really straightforward. But, weirdly, it's not. If I do:

$ complete -C "echo one two three" test.sh

and then type test.sh and hit [tab] one time, I get that line filled out with

$ test.sh one two three test.sh  test.sh

back. What?! For comparison,

$ complete -W "one two three" test.sh

does exactly what I'd expect: it offers one, three, or two as possible completions (in sort order, of course).

I can't find any good documentation anywhere, so I'm experimenting…. maybe a semicolon helps? complete -C "echo one two three;" test.sh … Ah, okay, this helps some, because it removes that weird test.sh from the end. But it still fills out the full string one two three. This is odd, because that description above definitely says "possible completions", plural.
… so, hmmm, ah, newlines? Let's try complete -C "echo -e 'one\ntwo\nthree';" test.sh.

Okay, so, this is promising. If I type test.sh [tab], I get back one three two. But then, things go wrong. I add an o to resolve the ambiguity, so, test.sh o[tab], and that returns

o      one    three  two

Which is puzzling. In fact, any string I type gets added to the list; if I do test.sh wtf[tab], I get one three two wtf echoed back. I guess possibly I need to do something sophisticated with compgen, but, again, I stress, this works as expected with -W. And, in fact

$ complete -W '$(echo one two three)' test.sh

works just fine (note single quotes to prevent premature expansion/execution, in case you do something more sophisticated than echo which could produce variable results).

What am I missing here?

Best Answer

It's the job of the -C command to figure out what text to insert in response to a completion request. The interface is a bit strange:

  • The argument to -C is interpreted as a bash script snippet. Bash appends three properly-quoted strings to this snippet:

    • the command whose arguments are being completed;
    • the prefix of the word where the cursor is, up to the cursor;
    • the word before the cursor.
  • Some variables are added to the environment:

    • COMP_LINE contains the complete line in which completion is performed.
    • COMP_POINT contains the cursor position inside COMP_LINE (counting from 0).
    • COMP_TYPE is 9 for normal completion, 33 when listing alternatives on ambiguous completions, 37 for menu completion, 63 when tabbing between ambiguous completions, 64 to list completions after a partial completion.
    • COMP_KEY contains the key that triggered completion (e.g. a tab character if the user pressed Tab).
  • The output of the command is interpreted as text to replace the current word with. Note that this is a replacement, not a suffix to append; the command is supposed filter by the given prefix. It may contain multiple lines, in which case each line is considered a possible replacement.

This is roughly the same mechanism that is provided, and better documented, for functions with -F. Functions get a somewhat saner interface: they can read the list of words of the current command in the COMP_WORDS variable, and the starting position of the current word in COMP_CWORD; they write the results in the COMPREPLY array.

Since the useful arguments — in particular the prefix to complete — are passed at the end of the command, -C is pretty much only usable with an external command. Of course you can use something like sh -c …, but it won't be pretty.

complete -C 'sh -c '\'for x in one two three; do case $x in "$1"*) echo "$x";; esac''\'
Related Question