带有等号的完整命令选项

时间:2015-04-19 10:54:07

标签: bash

我正在尝试为可能在--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

所以它几乎可以工作除了我在完成列表中得到一个虚假的'='标志..有什么建议吗?

2 个答案:

答案 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