So as an example of a program that does this properly.
If you type:
git stash list
in a terminal
then move your cursor over to the "h"
and press TAB your output will be:
git stash h list
and your cursor would be on the last "h"
I thought that adding the -S (suffix) command to my compgen would append a space, but alas, it does not.
COMPREPLY=($(compgen -W "$1" -S " " -- ${cur}))
And as an example, my program would instead output:
git stashh list.
How do I get the desired behavior? Is my suffix approach correct? Do I need to get bash to ignore the fact that it is a white space?
Should I add -o nospace so that I always get the exact amount of spaces I expect? Adding -o nospace anywhere in my COMPREPLY doesn't seem eliminate the spaces? Maybe I'm not using correct?
command with -o nospace for the record is:
COMPREPLY=($(compgen -o nospace -W "$1" -S " " -- ${cur}))
Related
How do I remove a single "command" from Bash's auto complete command suggestions? I'm asking about the very first argument, the command, in auto complete, not asking "How to disable bash autocomplete for the arguments of a specific command"
For example, if I have the command ls and the system path also finds ls_not_the_one_I_want_ever, and I type ls and then press tab, I want a way to have removed ls_not_the_one_I_want_ever from every being a viable option.
I think this might be related to the compgen -c list, as this seems to be the list of commands available.
Background: WSL on Windows is putting all the .dll files on my path, in addition to the .exe files that should be there, and so I have many dlls I would like to remove in my bash environment, but I'm unsure how to proceed.
Bash 5.0's complete command added a new -I option for this.
According to man bash —
complete -pr [-DEI] [name ...]
[...] The -I option indicates that other supplied options and actions should apply to completion on the initial non-assignment word on the line, or after a command delimiter such as ; or |, which is usually command name completion. [...]
Example:
function _comp_commands()
{
local cur=$2
if [[ $cur == ls* ]]; then
COMPREPLY=( $(compgen -c "$cur" | grep -v ls_not_wanted) )
fi
}
complete -o bashdefault -I -F _comp_commands
Using #pynexj's answer, I came up with the following example that seems to work well enough:
if [ "${BASH_VERSINFO[0]}" -ge "5" ]; then
function _custom_initial_word_complete()
{
if [ "${2-}" != "" ]; then
if [ "${2::2}" == "ls" ]; then
COMPREPLY=($(compgen -c "${2}" | \grep -v ls_not_the_one_I_want_ever))
else
COMPREPLY=($(compgen -c "${2}"))
fi
fi
}
complete -I -F _custom_initial_word_complete
fi
I want to have a command autocomplete uploaded scripts that would be run remotely, but the user could also pick a local script to upload. Here is a small example to illustrate the problem I have with the bash complete logic.
_test_complete()
{
local cur prev opts uploaded_scripts
uploaded_scripts='proc1.sh proc2.sh'
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ ${prev} == '-s' ]] ; then
COMPREPLY=( $(compgen -W "${uploaded_scripts}" -- ${cur}) )
return 0
fi
}
complete -F _test_complete remote
The example almost works but it does not autocomplete local file searches anymore.
$ remote -s proc<TAB><TAB>
proc1.sh proc2.sh
$ remote -s proc1.sh ./<TAB><TAB>
Nothing happens when you do the usual file search ./ which should list files in current dir. Any ideas on how you can enable both ?
EDIT: The above example had a problem you could only pick one file with file complete. I hacked a solution which works but if anyone has a better one please leave a comment. Also with the -o default from the accepted answer.
_test_complete()
{
local cur prev opts uploaded_scripts
uploaded_scripts='proc1.sh proc2.sh'
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
[[ $COMP_CWORD == '1' ]] && LAST_OPT=''
[[ ${prev:0:1} == '-' ]] && LAST_OPT=${prev}
if [[ ${LAST_OPT} == '-s' ]]; then
COMPREPLY=( $(compgen -o default -W "${uploaded_scripts}" -- ${cur}) )
return 0
fi
}
complete -F _test_complete remote
You can use complete's -o default option (Usually I'd use both -o default and -o bashdefault):
complete -o default -F _test_complete remote
According to man bash:
bashdefault
Perform the rest of the default bash completions if the compspec generates no matches.
default
Use readline's default filename completion if the compspec generates no matches.
You just have to add all files from the local directory to COMPREPLY too. complete -f -- abc generates a list of files starting with abc.
By the way: Instead of "${COMP_WORDS[COMP_CWORD]}" and COMP_CWORD-1 you can also use $2 and $3 which are supplied to any completion function by bash.
But here I completely dropped the if since it seems you want to allow multiple files after -s. Since you don't suggest -s itself, just suggest the files all the time:
_test_complete() {
local cur="$2" prev="$3" uploaded_scripts='proc1.sh proc2.sh'
COMPREPLY=( $(
compgen -W "${uploaded_scripts}" -- "$cur"
compgen -f -- "$cur"
) )
}
complete -F _test_complete remote
Note: COMPREPLY=( $(...) ) is easy to write but has some flaws. Files with spaces in them will be split into multiple suggestions and special symbols like * will expand and generate even more suggestions. To avoid this, either set IFS=$'\n'; set -o noglob or use mapfile -t COMPREPLY < <(...).
After you have done this, you can use complete -o filenames -F ... such that those problematic suggestions are correctly quoted when being inserted too.
Use case: I am trying to get source activate <env> to autocomplete the names of my conda environments (i.e. the list of directories in ~/anaconda3/envs/).
I've managed to get it to work if I didn't need the 'activate' in there using this code:
_source ()
{
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=($(ls ~/anaconda3/envs | xargs -I dirs bash -c "compgen -W dirs $cur"))
return 0
}
complete -F _source source
I've tried setting the last argument of complete to source\ activate and 'source activate' but that's not working (it just autocompletes with local files).
The issue seems to be that because source activate is not a function, it doesn't pick it up.
The simple solution is, of course, to make a single-word bash script which just contains source activate $1. But I'd rather do it properly!
My solution was adapted from this conda issue.
It checks that the first argument is activate and then does the compgen using whatever the second word you're typing is.
#!/bin/bash
_complete_source_activate_conda(){
if [ ${COMP_WORDS[COMP_CWORD-1]} != "activate" ]
then
return 0
fi
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=($(ls ~/anaconda3/envs | xargs -I dirs bash -c "compgen -W dirs $cur"))
return 0
}
complete -F _complete_source_activate_conda source
Put that script in /etc/bash_completion.d/. Unfortunately it kills the first-word autocompletion - with some more fiddling it would probably be able to handle both.
getting autocomplete to work for the second argument can be done with a case statement
like this
_source()
{
local cur prev opts
case $COMP_CWORD in
1) opts="activate";;
2) [ ${COMP_WORDS[COMP_CWORD-1]} = "activate" ] && opts=$(ls ~/anaconda3/envs | xargs -I dirs bash -c "compgen -W dirs $cur");;
*);;
esac
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return 0
}
complete -F _source source
compgen is build in function of bash which you then give the options you want to complete for with $opts for the first argument it will authocomplete "activate" but that can be adapted to be a more existive list and for the second argument it first checks if the first argument is "activate" before attempting to complete
disclaimer i adapted this from a completion function i wrote for ubports-qa, i haven't been able to test it but it should just work
Say I have a command named "foo" that takes one argument (either "encrypt" or "decrypt") and then a list of files. I want to write a bash completion script that helps with the first argument and then enables standard file completion for the others. The closest I've been able to come is this:
complete -F _foo foo
function _foo()
{
local cmds cur
if [ $COMP_CWORD -eq 1 ]
then
cur="${COMP_WORDS[1]}"
cmds=("encrypt decrypt")
COMPREPLY=($(compgen -W "${cmds}" -- ${cur}) )
else
COMPREPLY=($(compgen -f ${COMP_WORDS[${COMP_CWORD}]} ) )
fi
}
This does the first argument correctly and will chose a filename from the current directory for the subsequent ones. But say the current directory contains a subdirectory bar that contains a file baz.txt. After typing ba-TAB-TAB, completion results in "bar " (space after the "r") and is ready to choose the next argument. What I want is the standard bash completion, where the result is "bar/" (no space after the slash), ready to choose a file in the subdirectory. Is there any way to get that?
I know this is a little late, but I have found a solution here: https://stackoverflow.com/a/19062943/108105
Basically, you use complete -o bashdefault -o default, and when you want to revert to the default bash completion you set COMPREPLY=(). Here's an example:
complete -o bashdefault -o default -F _foo foo
_foo() {
local cur=${COMP_WORDS[COMP_CWORD]}
if (( $COMP_CWORD == 1 )); then
COMPREPLY=( $(compgen -W 'encrypt decrypt' -- "$cur") )
else
COMPREPLY=()
fi
}
The bash documentation can be a little enigmatic. The simplest solution is to alter the completion function binding:
complete -o filenames -F _foo foo
This means the function returns filenames (including directories), and special handling of the results is enabled.
(IMHO the documentation doesn't make it clear that this effectively post-processes COMPREPLY[] as set by your completion function; and that some of the -o options, that one included, when applied to compgen appear to have no effect.)
You can get closer to normal bash behaviour by using:
complete -o filenames -o bashdefault -F _foo foo
that gets you "~" completion back.
There are two problems with the above however:
if you have a directory named "encrypt" or "decrypt" then the expansion of your keywords will grow a trailing "/"
$VARIABLE expansion won't work, $ will become \$ to better match a filename with a $. Similarly #host expansion won't work.
The only way that I have found to deal with this is to process the compgen output, and not rely on the "filenames" post-processing:
_foo()
{
local cmds cur ff
if (($COMP_CWORD == 1))
then
cur="${COMP_WORDS[1]}"
cmds="encrypt decrypt"
COMPREPLY=($(compgen -W "$cmds" -- "$cur"))
COMPREPLY=( "${COMPREPLY[#]/%/ }" ) # add trailing space to each
else
# get all matching files and directories
COMPREPLY=($(compgen -f -- "${COMP_WORDS[$COMP_CWORD]}"))
for ((ff=0; ff<${#COMPREPLY[#]}; ff++)); do
[[ -d ${COMPREPLY[$ff]} ]] && COMPREPLY[$ff]+='/'
[[ -f ${COMPREPLY[$ff]} ]] && COMPREPLY[$ff]+=' '
done
fi
}
complete -o bashdefault -o default -o nospace -F _foo foo
(I also removed the superfluous array for cmd in the above, and made compgen more robust and handle spaces or leading dashes in filenames.)
The downsides now are that when you get the intermediate completion list (i.e. when you hit tab twice to show multiple matches) you won't see a trailing / on directories, and since nospace is enabled the ~ $ # expansions won't grow a space to cause them to be accepted.
In short, I do not believe you can trivially mix-and-match your own completion and the full bash completion behaviour.
Bash tab completion adds extra space after the first completion which stops further completion if the compeletion target is a file in multi-level folders.
For example, I have a file in the path ~/Documents/foo/bar.txt, and I want to list it.
I face the following problem, when input
a#b:~$ls Docu <TAB>
I get
a#b:~$ls Documents |(<-this is the cursor, so there is an extra space afer Documents)
So I cannot further tab complete. I have to backspace to delete the extra space.
Normally I want to get:
a#b:~$ls Docu <TAB>
a#b:~$ls Documents/ <TAB>
a#b:~$ls Documents/foo/ <TAB>
a#b:~$ls Documents/foo/bar.txt
Just for the record: There is also a bug in the adobereader-enu (acroread) package that breaks bash completion. In this case you can just delete the symlink:
rm /etc/bash_completion.d/acroread.sh
See also: https://bugs.launchpad.net/ubuntu/+source/acroread/+bug/769866
I have had this same problem with my bash completion in both Ubuntu 11.10 and 12.04. I found that I was able to get many commands to start working correctly by editing /etc/bash_completion. Specifically, I commented out the following section:
####
# makeinfo and texi2dvi are defined elsewhere.
#
#for i in a2ps awk bash bc bison cat colordiff cp csplit \
# curl cut date df diff dir du enscript env expand fmt fold gperf gprof \
# grep grub head indent irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \
# mv netstat nl nm objcopy objdump od paste patch pr ptx readelf rm rmdir \
# sed seq sha{,1,224,256,384,512}sum shar sort split strip tac tail tee \
# texindex touch tr uname unexpand uniq units vdir wc wget who; do
# have $i && complete -F _longopt -o default $i
#done
Now ls works well again. I have not figured out yet why mv is still mis-behaving.
This has been answered here at askubuntu. It is related to the bug here
Relevant answer from the above thread:
edit /etc/bash_completion line 1587, change default to filenames (make a backup first).
i also got around the problem by changing
_filedir with _filedir_pdf
in /etc/bash_completion.d/acroread.sh
(Ubuntu 12.04)
acroread bash completion changes the _filedir function thereby altering the behaviour of a lot of other alsobash completion functions