How to prevent bash completion from replacing a character when tab completing

bash, bash-completion

Solution

So, this intrigued me, so I've been reading through the bash completion source (available at /etc/bash_completion).

I came across this variable: `${COMP_WORDBREAKS}`, which appears to allow control over what characters are used to delimit words.

I also came across this function, `_get_cword` and the complementary `_get_pword`, both recommended for use in place of `${COMP_WORDS[COMP_CWORD]}` and `${COMP_WORDS[COMP_CWORD-1]}` respectively.

So, putting all this together, I did some testing and here's what I've come up with: this seems to work for me, at least, hopefully it will for you too:

# maintain the old value, so that we only affect ourselves with this
OLD_COMP_WORDBREAKS=${COMP_WORDBREAKS}
COMP_WORDBREAKS="${COMP_WORDBREAKS}@"

cur="$(_get_cword)"
prev="$(_get_pword)"

if [[ "$cur" == '=@' ]]; then
    COMPREPLY=( $(compgen -f ${cur:2}) )

    # restore the old value
    COMP_WORDBREAKS=${OLD_COMP_WORDBREAKS}
    return 0
fi

if [[ "$prev" == '=@' ]]; then
    COMPREPLY=( $(compgen -f ${cur}) )

    # restore the old value
    COMP_WORDBREAKS=${OLD_COMP_WORDBREAKS}
    return 0

fi

Now, I'll admit that the split `if` cases are a little dirty, there's definitely a better way to do it but I need more caffeine.

Further, for whatever it's worth to you I also discovered along the way the `-P <prefix>` argument to `compgen`, which would prevent you from having to loop over the `$COMPREPLY[*]}` array after calling `compgen`, like so

COMPREPLY=( $(compgen -P @ -f ${cur:1}) )

That's a little redundant in the face of a full solution, though.

Problem

I'm building a bash completion script for a tool which shares file uploading semantics with curl. With curl, you can do: curl -F var=@file to upload a file. My application has similar semantics and I wish to be able to show possible files after the '@' is pressed. Unfortunately, this is proving difficult: ``` cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "$cur" == @* && "$prev" == '=' ]]; then COMPREPLY=( $(compgen -f ${cur:1}) ) return 0 fi ``` So if a command (so far) ends with: ``` abc=@ ``` Files in the current directory will show. ``` var=@/usr/ /usr/bin /usr/games ``` Problem is if I actually hit tab to complete, the '@' goes away! ``` var=/usr/bin ``` So it looks like bash replaces the entire current word with the tabbed COMPREPLY. The only way to avoid this has been to do this: ``` COMPREPLY=( $(compgen -f ${cur:1}) ) for (( i=0; i<${#COMPREPLY[@]}; i++ )); do COMPREPLY[$i]='@'${COMPREPLY[$i]} done ``` But now the tab completion looks weird to say the least: ``` @/usr/bin @/usr/games ``` Is there anyway to show a normal file tab completion (without the '@' prefix) but preserve the '@' when hitting tab?

Original source