使用空格完成bash选项卡

ger*_*imo 5 bash bash-completion

当可能的选项可能包含空格时,我遇到了bash-completion的问题.

假设我想要一个与第一个参数相呼应的函数:

function test1() {
        echo $1
}
Run Code Online (Sandbox Code Playgroud)

我生成一个可能的完成选项列表(一些有空格,一些没有),但我无法正确处理空格.

function pink() {
    # my real-world example generates a similar string using awk and other commands
    echo "nick\ mason syd-barrett david_gilmour roger\ waters richard\ wright"
}

function _test() {
    cur=${COMP_WORDS[COMP_CWORD]}
    use=`pink`
    COMPREPLY=( $( compgen -W "$use" -- $cur ) )
}
complete -o filenames -F _test test
Run Code Online (Sandbox Code Playgroud)

当我尝试这个时,我得到:

$ test <tab><tab>
david_gilmour  nick           roger          waters
mason          richard        syd-barrett    wright
$ test r<tab><tab>
richard  roger    waters   wright
Run Code Online (Sandbox Code Playgroud)

这显然不是我的意思.

如果我没有分配一个数组COMPREPLY,即只有$( compgen -W "$use" -- $cur ),如果只剩下一个选项,我会让它工作:

$ test n<tab>
$ test nick\ mason <cursor>
Run Code Online (Sandbox Code Playgroud)

但如果仍有几个选项,它们都用单引号打印:

$ test r<tab><tab>
$ test 'roger waters
richard wright' <cursor>
Run Code Online (Sandbox Code Playgroud)

我的COMPREPLY变量肯定有问题,但我无法弄清楚是什么......

(在solaris上运行bash,以防万一...)

amp*_*ine 5

如果需要处理字符串中的数据,可以使用Bash的内置字符串替换运算符.

function _test() {
    local iter use cur
    cur=${COMP_WORDS[COMP_CWORD]}
    use="nick\ mason syd-barrett david_gilmour roger\ waters richard\ wright"
    # swap out escaped spaces temporarily
    use="${use//\\ /___}"
    # split on all spaces
    for iter in $use; do
        # only reply with completions
        if [[ $iter =~ ^$cur ]]; then
            # swap back our escaped spaces
            COMPREPLY+=( "${iter//___/ }" )
        fi
    done
}
Run Code Online (Sandbox Code Playgroud)


ric*_*ici 5

令人讨厌的是,可能包含空格的自定义制表符完成单词。据我所知,没有完美的解决方案。也许将来的某个版本compgen会足够好以产生一个数组,而不是一次只输出一行,甚至接受数组中的wordlist参数。但是直到那时,以下方法可能会有所帮助。

理解问题很重要,因为问题是( $(compgen ... ) )通过将compgen命令的输出分割为中的字符$IFS(默认情况下为任何空白字符)而产生的数组。因此,如果compgen返回:

roger waters
richard wright
Run Code Online (Sandbox Code Playgroud)

然后COMPREPLY将有效地设置为array (roger waters richard wright),总共完成四个可能的操作。如果改用( "$(compgen ...)"),则会COMPREPLY将其设置为数组($'roger waters\nrichard wright'),该数组只有一个可能的补全(补全内包含换行符)。这些都不是您想要的。

如果所有可能的补全都没有换行符,则可以compgen通过临时重置IFS然后恢复它来将换行符拆分为换行符。但是我认为一个更优雅的解决方案是只使用mapfile

_test () { 
    cur=${COMP_WORDS[COMP_CWORD]};
    use=`pink`;
    ## See note at end of answer w.r.t. "$cur" ##
    mapfile -t COMPREPLY < <( compgen -W "$use" -- "$cur" )
}
Run Code Online (Sandbox Code Playgroud)

mapfile命令由放置在发送线路compgenstdout到阵列COMPREPLY。(该-t选项会导致从每行中删除尾随的换行符,这几乎总是您使用时想要的mapfile。请参阅help mapfile以获取更多选项。)

这没有解决问题的其他烦人部分,即将单词表修改为可以接受的形式compgen。由于compgen不允许有多个-W选项,也不接受数组,因此唯一的选择是格式化字符串,以使bash单词拆分(带引号和全部)将生成所需的列表。实际上,这意味着手动添加转义符,就像您在函数中所做的那样pink

pink() {
    echo "nick\ mason syd-barrett david_gilmour roger\ waters richard\ wright"
}
Run Code Online (Sandbox Code Playgroud)

但这很容易发生事故并且令人讨厌。更好的解决方案将允许直接指定替代方案,特别是如果以某种方式生成替代方案时。生成可能包含空格的替代方法的一种好方法是将它们放入数组中。给定一个数组,您可以充分利用printf%q格式为以下内容生成一个正确引用的输入字符串compgen -W

# This is a proxy for a database query or some such which produces the alternatives
cat >/tmp/pink <<EOP
nick mason
syd-barrett
david_gilmour
roger waters
richard wright
EOP

# Generate an array with the alternatives
mapfile -t pink </tmp/pink

# Use printf to turn the array into a quoted string:
_test () { 
    mapfile -t COMPREPLY < <( compgen -W "$(printf '%q ' "${pink[@]}")" -- "$2" )
}
Run Code Online (Sandbox Code Playgroud)

如所写,该完成函数不会以bash接受为单个单词的形式输出完成。换句话说,完成roger watersroger waters代替生成的roger\ waters。在(可能)目标是产生正确引用的补全的情况下,有必要在compgen过滤补全列表之后再次添加转义:

_test () {
    declare -a completions
    mapfile -t completions < <( compgen -W "$(printf '%q ' "${pink[@]}")" -- "$2" )
    local comp
    COMPREPLY=()
    for comp in "${completions[@]}"; do
        COMPREPLY+=("$(printf "%q" "$comp")")
    done
}
Run Code Online (Sandbox Code Playgroud)

注:我取代的计算$cur$2,因为该函数调用通过complete -F传递的命令$1和单词被作为完成$2。(它的前一个单词也传递为$3。)而且,引用它很重要,这样它在进入时就不会被单词分割compgen