2

How to write autocomplete in bash, so that if I have:

mycommand first_argument|garbage

where | denotes the cursor, it should pass "first_argument" and not "first_argumentgarbage" to compgen?

In the examples I have it behaves in the wrong way

COMPREPLY=( $(compgen -W "add remove list use current" -- "$cur") ) # buggy
pihentagy
  • 5,975
  • 9
  • 39
  • 58
  • 1
    As is, your example doesn't show anything. What do you mean by buggy ? have you sourced your completion script ? What is the content of `$cur` ? What does `echo ${COMP_WORDS[COMP_CWORD]}` displays when you put it just before your `COMPREPLY` declaration ? – Aserre Nov 30 '16 at 15:09
  • Press space, left, tab? – Maxim Egorushkin Nov 30 '16 at 16:07
  • @Aserre: `cur=${COMP_WORDS[COMP_CWORD]}`. Since no space is at the cursor, of course it is first_argumentgarbage. – pihentagy Dec 01 '16 at 09:45

1 Answers1

3

Bash completion uses a lot of different variables. Some of them are used to process input and determine which parameter to complete.

For the following explanation, I will use this test input (with | as the cursor) :

./test.sh ad re|garbage
  • ${COMP_WORDS} : contains all the words of the input in the form of an array. In this case, it contains : ${COMP_WORDS[@]} == {"./test.sh", "ad", "regarbage"}
    • The word separator characters are found in the $COMP_WORDBREAKS variable
  • $COMP_CWORD : contains the position of the word the cursor is curently selecting. In this case, it contains : $COMP_CWORD == 2
  • $COMP_LINE : contains the whole input in form of string. In this case, it contains : $COMP_LINE == "./test.sh ad regarbage"
  • $COMP_POINT : contains the position of the cursor in the whole line. In this case, it contains : $COMP_POINT == 15

Still using the same data, doing cur=${COMP_WORDS[COMP_CWORD]} will return the element at index 2 in the ${COMP_WORD} array, which is regarbage.

To circumvent this behaviour, you'll have to play around with the $COMP_LINE and $COMP_POINT variables as well. Here is what I came up with :

# we truncate our line up to the position of our cursor
# we transform the result into an array
cur=(${COMP_LINE:0:$COMP_POINT})

# we use ${cur} the same way we would use ${COMP_WORDS}
COMPREPLY=( $( compgen -W "add remove list use current" -- "${cur[$COMP_CWORD]}" ) )

Output :

> ./test2.sh ad re|garbage
# press TAB
> ./test2.sh ad remove|garbage

Note that by default, there will be no space between remove and garbage. You'll have to play around the completion mechanics if this is a behaviour you want.

Aserre
  • 4,916
  • 5
  • 33
  • 56
  • Thanks for the in-depth explanation. I knew most of the variables you mentioned, but I missed an elegant usage pattern, which you gave me with your last. Btw how is that `${cur[$COMP_CWORD]}` and `$cur[COMP_CWORD]}` both work? – pihentagy Dec 06 '16 at 07:46
  • The fact that `${cur[$COMP_CWORD]}` and `$cur[COMP_CWORD]` returns the same result is pure luck. You can read more about array dereferencing [here](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_02.html). – Aserre Dec 06 '16 at 09:20
  • I wouldn't call it pure luck. Mentioned page says it is something like putting the index inside `(( ))`: >The INDEXNR is treated as an arithmetic expression that must evaluate to a positive number. – pihentagy Dec 06 '16 at 09:59