I am trying to write a Bash completion script for commands that can take long options on the form --option
or --param=value
. If the user has already entered an option on the command line, that option should be excluded from the completion list (assuming it only makes sense to specify a given option once on the command line).
Here is a first try:
_myprog()
{
local cur="${COMP_WORDS[$COMP_CWORD]}"
local words=(--help --param1= --param-state --param2=)
_exclude_cmd_line_opts
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}
complete -F _myprog myprog
_exclude_cmd_line_opts() {
local len=$(($COMP_CWORD - 1))
local i
for i in "${COMP_WORDS[@]:1:$len}" ; do
[[ $i == --* ]] && words=( "${words[@]/$i}" )
done
}
If source this script source script.sh
and then write:
$ myprog --param1= <tab><tab>
I get the following completion list:
= --help --param2= --param-state
so it works almost except for that I get a spurious '='
sign in the completion list.. Any suggestions?
Entering an equal sign on the command line forces a word break due to the default content of COMP_WORDBREAKS
. The effect seems to be that the equal sign enters as a separate word in COMP_WORDS
. This is exploited in the following modification of _exclude_cmd_line_opts
:
_exclude_cmd_line_opts() {
local len=$(($COMP_CWORD - 1))
local i
for ((i=1 ; i<=len; i++)) ; do
local j="${COMP_WORDS[$i]}"
if [[ $j == --* ]] ; then
(( i<len )) && [[ ${COMP_WORDS[$(( i + 1))]} == '=' ]] && j="$j="
words=( "${words[@]/$j}" )
fi
done
}
The problem with the original version of _exclude_cmd_line_opts
was that ${words[@]/$j}
would give a spurious =
when for example words=(param1=)
and j="param1"
(note the missing trailing equal sign in $j
which was caused by COMP_WORDBREAKS
)..
Update
I discovered another peculiarity with this. The cases above worked fine because I never had to type <tab>
immediately after an =
sign. However, if for example words=(--param= --param-info)
and I type --par<tab>
there is still two candidate completions and the current words is only partially completed to become --param
. At this I would like to select the first of the two candidates, and I type an explicit =
sign on the command line and then type <tab>
what happens now is that Bash thinks that you have typed a space (since COMP_WORDBREAKS contains =
) and the current completion word changes from --param=
to =
. This again, will make Bash readline omit insert the usual space, so the user is forced to type a space to continue completing next option.
It is possible to avoid having to type a space in the above case, by returning a COMPREPLY
array with an empty string.
_myprog()
{
local cur="${COMP_WORDS[$COMP_CWORD]}"
local prev=""
(( COMP_CWORD > 0 )) && prev="${COMP_WORDS[$(( COMP_CWORD - 1))]}"
[[ $cur == '=' && $prev == --* ]] && { COMPREPLY=( "" ); return; }
local words=(--param= --param-info)
_exclude_cmd_line_opts
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}
This solution takes advantage of _init_completion
function. It does not complete arguments for options that take such. It also accepts long options in form --param value
. This does not examine line after cursor position for excluding options, but it could be modified to take into account whole command line, if needed.
_exclude_cmd_line_opts() {
local i w j skip= sep_arg
for ((i=1 ; i<cword; i++)) ; do
[[ $skip ]] && { skip=; continue; }
w=${words[$i]}
if [[ $w == --* ]] ; then
[[ $w == --?*=* ]] && {
w=${w%%=*}
sep_arg=
} || sep_arg=1
for j in ${!options[@]}; do
[[ ${options[$j]%=} == $w ]] && {
[[ ${options[$j]} == *= && $sep_arg ]] && skip=1
unset -v options[$j]
break
}
done
fi
done
}
_myprog()
{
IFS=$'\n'
local cur prev words cword split # needed by _init_completion()
# Do not treat = as word breaks even if they are in $COMP_WORDBREAKS:
# Split option=value into option in $prev and value in $cur
_init_completion -s || return
local options=(--param= --param-info --opt --opt-suffix)
[[ $prev == --param ]] && { COMPREPLY=( ); return 0; }
_exclude_cmd_line_opts
local i
for i in ${!options[*]}; do [[ ${options[$i]} == *= ]] || options[$i]+=' ' ; done
COMPREPLY=( $(compgen -W "${options[*]}" -- "$cur") )
compopt -o nospace
} && complete -F _myprog myprog
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