当可能的选项可能包含空格时,我遇到了bash-completion的问题。
假设我想要一个与第一个参数相呼应的函数:
function test1() {
echo $1
}
我生成一个可能的完成选项列表(一些有空格,一些没有),但我无法正确处理空格。
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
当我尝试这个时,我得到:
$ test <tab><tab>
david_gilmour nick roger waters
mason richard syd-barrett wright
$ test r<tab><tab>
richard roger waters wright
这显然不是我的意思。
如果我没有为COMPREPLY
分配一个数组,即只有$( compgen -W "$use" -- $cur )
,如果只剩下一个选项,我可以使用它:
$ test n<tab>
$ test nick\ mason <cursor>
但如果仍有几个选项,它们都用单引号打印:
$ test r<tab><tab>
$ test 'roger waters
richard wright' <cursor>
我的COMPREPLY
变量肯定有问题,但我无法弄清楚是什么......
(在solaris上运行bash,以防万一...)
如果需要处理字符串中的数据,可以使用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
}
自定义制表符完成可能包含空格的单词非常困难。据我所知,没有优雅的解决方案。也许某些未来版本的compgen
会产生一个数组,而不是一次输出一行,甚至可以接受数组中的wordlist参数。但在此之前,以下方法可能有所帮助。
理解这个问题很重要,因为( $(compgen ... ) )
是一个通过将compgen
命令的输出分割为$IFS
中的字符而生成的数组,默认情况下是任何空白字符。所以,如果compgen
回归:
roger waters
richard wright
然后COMPREPLY
将有效地设置为阵列(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" )
}
mapfile
命令将compgen
发送到stdout
的行放入数组COMPREPLY
。 (-t
选项会导致从每一行删除尾部换行符,这几乎总是您在使用mapfile
时所需的内容。有关更多选项,请参阅help mapfile
。)
这并不涉及问题的另一个令人讨厌的部分,即将wordlist重写为compgen
可接受的形式。由于compgen
不允许多个-W
选项,也不接受数组,唯一的选择是以这样的方式格式化字符串,即bash
分词(带引号和全部)将生成所需的列表。实际上,这意味着手动添加转义,就像在函数pink
中所做的那样:
pink() {
echo "nick\ mason syd-barrett david_gilmour roger\ waters richard\ wright"
}
但那是容易发生事故和烦人的事。更好的解决方案将允许直接指定替代品,特别是如果以某种方式生成替代品。生成可能包含空格的替代方法的一种好方法是将它们放入数组中。给定一个数组,你可以充分利用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" )
}
如上所述,该完成函数不会以bash作为单个单词接受的形式输出完成。换句话说,完成roger waters
生成为roger 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
}
注意:我用$cur
替换了$2
的计算,因为通过complete -F
调用的函数作为$1
传递命令,并且单词被完成为$2
。 (它也传递了前一个词作为$3
。)另外,引用它是很重要的,这样它就不会在进入compgen
的途中被分词。
好吧,这个疯狂的装置很大程度上依赖于rici的解决方案,不仅完全有效,而且还引用任何需要它的完成,只有那些。
pink() {
# simulating actual awk output
echo "nick mason"
echo "syd-barrett"
echo "david_gilmour"
echo "roger waters"
echo "richard wright"
}
_test() {
cur=${COMP_WORDS[COMP_CWORD]}
mapfile -t patterns < <( pink )
mapfile -t COMPREPLY < <( compgen -W "$( printf '%q ' "${patterns[@]}" )" -- "$cur" | awk '/ / { print "\""$0"\"" } /^[^ ]+$/ { print $0 }' )
}
complete -F _test test
因此,就我可以测试它而言,它完全实现类似ls
的行为,减去路径特定的部分。
这是_test
函数的更详细版本,因此它变得更容易理解:
_test() {
local cur escapedPatterns
cur=${COMP_WORDS[COMP_CWORD]}
mapfile -t patterns < <( pink )
escapedPatterns="$( printf '%q ' "${patterns[@]}" )"
mapfile -t COMPREPLY < <( compgen -W "$escapedPatterns" -- "$cur" | quoteIfNeeded )
}
quoteIfNeeded() {
# Only if it contains spaces. Otherwise return as-is.
awk '/ / { print "\""$0"\"" } /^[^ ]+$/ { print $0 }'
}
这甚至都没有针对效率进行远程优化。然后,这只是标签完成,并没有导致任何相当大的完成列表明显延迟。
awk
将mapfile
输出拉成阵列。%q
后面有一个空间作为分离标记。$cur
,非常重要!compgen
的输出。并且只有它包含空格。mapfile
调用将输出提供给COMPREPLY。-o filenames
。它只适用于所有这些技巧。如果缺少一个,它就会失败。相信我;我试过了。 ;)