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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With