我正在尝试为可能在--option
或--param=value
表单上使用长选项的命令编写Bash完成脚本。如果用户已经在命令行中输入了一个选项,那么该选项应该从完成列表中排除(假设只在命令行上指定一次给定选项才有意义。)
这是第一次尝试:
_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
}
如果请求此脚本source script.sh
,然后写:
$ myprog --param1= <tab><tab>
我得到以下完成列表:
= --help --param2= --param-state
所以它几乎可以工作除了我在完成列表中得到一个虚假的'='
标志..有什么建议吗?
答案 0 :(得分:2)
在命令行中输入等号会因默认内容COMP_WORDBREAKS
而导致单词中断。效果似乎是等号在COMP_WORDS
中作为单独的单词输入。这在_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
}
_exclude_cmd_line_opts
的原始版本存在的问题是${words[@]/$j}
会在例如=
和words=(param1=)
时产生虚假的j="param1"
(请注意缺少的尾随$j
引起的COMP_WORDBREAKS
等号。
更新
我发现了另一个特点。上述案例运作正常,因为我从未在<tab>
符号后立即键入=
。但是,如果例如words=(--param= --param-info)
和我输入--par<tab>
,则仍有两个候选完成,并且当前单词仅部分完成以成为--param
。在此我想选择两个候选中的第一个,并在命令行上键入一个显式的=
符号然后键入<tab>
现在发生的事情是Bash认为你输入了一个空格(因为COMP_WORDBREAKS包含=
),当前完成字从--param=
更改为=
。这再次,将使Bash readline省略插入通常的空格,因此用户被迫键入空格以继续完成下一个选项。
通过返回带有空字符串的COMPREPLY
数组,可以避免在上述情况下键入空格。
_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") )
}
答案 1 :(得分:1)
此解决方案利用了_init_completion
功能。它不会为采用此选项的选项填写参数。它还接受格式为--param value
的长选项。这不会检查光标位置后的行是否包含排除选项,但是可以根据需要进行修改以考虑整个命令行。
_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