【问题标题】:Compgen doesn't complete words containing colons correctlyCompgen 无法正确完成包含冒号的单词
【发布时间】:2020-12-10 10:59:03
【问题描述】:

我在创建 Bash 补全函数时遇到问题,该命令应包含冒号。当您键入命令并按 Tab 键时,Bash 会将命令行的内容插入到一个数组中,只有这些数组由冒号分隔。所以命令:

假富:苹果

将变为: ('dummy' 'foo' ':' 'apple')

我知道一种解决方案是更改 COMP_WORDBREAKS,但这不是一个选项,因为它是一个团队环境,我可能会通过弄乱 COMP_WORDBREAKS 来破坏其他代码。

然后this answer 建议使用_get_comp_words_by_ref__ltrim_colon_completions 变量,但从答案中我并不清楚如何使用这些变量。

所以我在下面尝试了不同的解决方案。基本上,将命令行读取为字符串,并通过计算“偏移量”来确定用户光标当前正在选择的单词。如果命令行中有一个冒号,它的左边或右边有文本,它会将偏移量各加 1,然后从 COMP_CWORD 变量中减去它。

  1 #!/bin/bash
  2 _comp() {
  3     #set -xv
  4     local a=("${COMP_WORDS[@]}")
  5     local index=`expr $COMP_CWORD`
  6     local c_line="$COMP_LINE"
  7                                                                             
  8     # Work out the offset to change the cursor by
  9     # This is needed to compensate for colon completions
 10     # Because COMP_WORDS splits words containing colons
 11     # E.g. 'foo:bar' becomes 'foo' ':' 'bar'.
 12     
 13     # First delete anything in the command to the right of the cursor
 14     # We only need from the cursor backwards to work out the offset.
 15     for ((i = ${#a[@]}-1 ; i > $index ; i--));
 16     do
 17         regex="*([[:space:]])"${a[$i]}"*([[:space:]])"
 18         c_line="${c_line%$regex}"
 19     done
 20     
 21     # Count instances of text adjacent to colons, add that to offset.
 22     # E.g. for foo:bar:baz, offset is 4 (bar is counted twice.)
 23     # Explanation: foo:bar:baz foo
 24     #              0  12  34   5   <-- Standard Bash behaviour
 25     #              0           1   <-- Desired Bash behaviour
 26     # To create the desired behaviour we subtract offset from cursor index.
 27     left=$( echo $c_line | grep -o "[[:alnum:]]:" | wc -l )
 28     right=$( echo $c_line | grep -o ":[[:alnum:]]" | wc -l )
 29     offset=`expr $left + $right`
 30     index=`expr $COMP_CWORD - $offset`
 31     
 32     # We use COMP_LINE (not COMP_WORDS) to get an array of space-separated
 33     # words in the command because it will treat foo:bar as one string.
 34     local comp_words=($COMP_LINE)
 35     
 36     # If current word is a space, add an empty element to array
 37     if [ "${COMP_WORDS[$COMP_CWORD]}" == "" ]; then
 38         comp_words=("${comp_words[@]:0:$index}" "" "${comp_words[@]:$index}"    )   
 39     fi
 40         
 41     
 42     local cur=${comp_words[$index]}
 43     
 44     local arr=(foo:apple foo:banana foo:mango pineapple)
 45     COMPREPLY=()
 46     COMPREPLY=($(compgen -W "${arr[*]}" -- $cur))
 47     #set +xv
 48 }   
 49 
 50 complete -F _comp dummy 

问题是,这仍然无法正常工作。如果我输入:

dummy pine<TAB>

然后它将正确完成dummy pineapple。如果我输入:

dummy fo<TAB>

然后它将显示三个可用选项foo:apple foo:banana foo:mango。到现在为止还挺好。但如果我输入:

dummy foo:<TAB>

然后我得到的输出是 dummy foo:foo: 然后其他选项卡不起作用,因为它将 foo:foo: 解释为 cur,它不匹配任何完成。

当我自己测试 compgen 命令时,如下所示:

compgen -W 'foo:apple foo:banana foo:mango pineapple' -- foo:

然后会返回三个匹配的结果:

foo:apple
foo:banana
foo:mango

所以我假设正在发生的是 Bash 补全看到它有一个空字符串和三个可用的补全候选,所以在命令行末尾添加前缀 foo: - 即使 foo: 已经要完成的光标。

我不明白如何解决这个问题。当不涉及冒号时,这很好用——“pine”总是会变成菠萝。如果我去更改数组以添加更多选项:

local arr=(foo:apple foo:banana foo:mango pineapple pinecone pinetree)
COMPREPLY=()
COMPREPLY=($(compgen -W "${arr[*]}" -- $cur))

然后当我输入dummy pine&lt;TAB&gt; 时,它仍然会愉快地显示pineapple pinecone pinetree,并且不会尝试在末尾添加多余的pine

这种行为有什么解决办法吗?

【问题讨论】:

  • 每个候选选项有多少个冒号?
  • bash 的补全支持很混乱。 bash-completion 项目包含一些很好的技巧来满足您的需求。我建议您花一些时间了解相关的辅助函数并重用它们。
  • 就我自己而言,我宁愿将字符串自动补全为foo.apple foo.banana,并将. 转换为:,再加一个
  • 另一种选择是在 shell 中键入时引用字符串,例如 dummy 'foo&lt;TAB&gt;
  • 每个候选选项中可能有多个冒号,或者没有。不幸的是,通过替换 . 而不是 : 或转义命令来更改命令行为不是一种选择,因为这不是我要编写的程序。

标签: bash tab-completion bash-completion compgen


【解决方案1】:

过去对我有用的一种方法是将compgen 的输出用单引号括起来,例如:

__foo_completions() {
  COMPREPLY=($(compgen -W "$(echo -e 'pine:cone\npine:apple\npine:tree')" -- "${COMP_WORDS[1]}" \
    | awk '{ print "'\''"$0"'\''" }'))
}

foo() {
  echo "arg is $1"
}

complete -F __foo_completions foo

然后:

$ foo <TAB>
$ foo 'pine:<TAB>
'pine:apple'  'pine:cone'   'pine:tree'
$ foo 'pine:a<TAB>
$ foo 'pine:apple'<RET>
arg is pine:apple
$ foo pi<TAB>
$ foo 'pine:


【讨论】:

    猜你喜欢
    • 2022-08-09
    • 1970-01-01
    • 2015-01-08
    • 2020-05-11
    • 1970-01-01
    • 1970-01-01
    • 2019-08-25
    • 2014-04-04
    • 1970-01-01
    相关资源
    最近更新 更多