【问题标题】:Simulate Bash's COMPREPLY response without actually completing it模拟 Bash 的 COMPREPLY 响应而不实际完成它
【发布时间】:2021-12-31 03:52:50
【问题描述】:

重现步骤

  • 在tmp上创建目录,在里面添加1个文件。

    mkdir /tmp/testdir && touch /tmp/testdir/examplefile
    
  • 将下面的脚本粘贴到/tmp/completion.sh

    # BEGIN AUTOCOMPLETE
    function _foo_complete() {
    local comnum cur opts
    [[ "${COMP_WORDS[@]}" == *"-"* ]] && comnum=2 || comnum=1;
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    opts="--help --restart -h -r"
    if (( COMP_CWORD > comnum )); then
     COMPREPLY=( $(for filename in "/tmp/testdir/"*; do echo ${filename##*/}; done) )
     return
    fi
    if [[ ${cur} == -* ]]; then
     COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
     return 0
    else
     COMPREPLY=( $(for filelist in /tmp/testdir/"$2"*; do echo ${filelist##*/}; done) )
    fi
    }
    complete -F _foo_complete foo.sh
    # END AUTOCOMPLETE
    
  • 然后获取它。

    . /tmp/completion.sh
    

预期结果

$ foo.sh --restart examplefile <tab><tab>
examplefile           
$ foo.sh --restart examplefile <tab><tab>
examplefile        
$ foo.sh --restart examplefile <tab><tab>
examplefile     
$ foo.sh --restart examplefile <tab><tab>

会发生什么

$ foo.sh --restart examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile <tab><tab> examplefile

我希望建议显示为可能的完成,但实际上并未完成(出于显示目的)。这个问题已经被问到before,但到目前为止还没有给出答案。

关于-o nosort

我研究了该选项,它仅适用于 Bash 4.4+,我在 16.04 机器上尝试过,但失败了。寻找更多全球ish解决方案

【问题讨论】:

  • 您是否希望它总是只列出当前目录中的所有文件?即如果你输入./myscript /usr/bin&lt;&lt;tab&gt;&lt;tab&gt;,它不应该列出/usr/usr/bin,而是当前目录?
  • @root 我已经更新了这个问题。创建完成文件以仅从硬编码目录中获取文件。
  • @pynexj 已编辑,感谢您指出这一点。
  • 为什么在这个问题上有赏金? @pynexj hack 为COMPREPLY 添加一个空白(也可以是空字符串'')应该足够好,不是吗?
  • @Frevadona 我忘了提,-o nosort 仅适用于 Bash 4.4+,我在 16.04 机器上尝试过,但它失败了。寻找更多“全球”的解决方案。

标签: bash autocomplete compgen


【解决方案1】:

你可以试试这个技巧;根据您的操作系统,这可能适合您:

注意:我将完成功能剥离为相关的TabTab功能

#!/bin/bash
mkdir -p /tmp/testdir && touch /tmp/testdir/examplefile

_foo_complete() {
    local dirpath=/tmp/testdir
    local nullglob=$( shopt -q nullglob && echo true || echo false )

    $nullglob || shopt -s nullglob
    COMPREPLY=( "$dirpath"/* )
    $nullglob || shopt -u nullglob

    COMPREPLY=( "${COMPREPLY[@]#"$dirpath"/}" )

    [ ${#COMPREPLY[@]} -eq 1 ] && COMPREPLY+=( $'\xC2\xA0' )
}
complete -F _foo_complete foo.sh

后记:

这与@pynexj answer 的想法基本相同:向COMPREPLY 添加第二个不可见的元素,从而禁用Tab 自动完成,同时保留Tab kbd>标签列表。

现在的问题是 bash 自动对 COMREPLY 进行排序,因此,ASCII space 字符在数字上是第一个 printable 字符,它将显示为列表的第一列。

为了解决这个问题,我尝试了 UTF-8 字符集中的几乎所有其他空白字符,它们都被排序到与 ASCII space 字符相同的等级;除了NON-BREAKING-SPACE,这就是我在这里使用的。

该修复不是完全证明,因为有很多 UTF-8 字符会在它之后进行排序,最重要的是,它会根据操作系统得到不同的处理:一些sort 实现会给它与相同的排名ASCII space 字符,而其他字符则不会。想一想,NON-BREAKING-SPACE 在概念上是什么? 这是一个空格,但同时也像一个字母!

【讨论】:

  • 我无法让您的解决方案正常工作,您能否使用我在问题中发布的要求/代码测试您的脚本?然而,它的 html 编码工作得很好。
  • 0xc2a0 是 utf8 non-breaking space 所以这个想法是相似的。不确定它是否适用于非 utf8 语言环境,也不确定它是否总是在排序时是最后一个。
  • @pynexj:是的,我一直在尝试不同的方法,现在我可以说没有其他方法可以获得预期的输出。使用&amp;nbsp; 有其局限性,但只要文件名以 ASCII 字符开头并且操作系统支持 UTF8 就可以使用。
  • @Liso 该代码仅用于说明 TAB TAB 完成。我会更新它以实现原始功能的功能(虽然今天不可能)
  • @pynexj 似乎排序因操作系统而异。使用en_US-utf-8 作为 LANG/LC_ALL,它适用于 macOS Mojave/Catalina、FreeBSD 11/12、RedHat 7/8,但适用于 Solaris 11.3/11.4 和 macOS BigSur
【解决方案2】:

尝试以下compspec.sh(基于 OP 的代码):

function _foo_complete()
{
    local comnum cur opts

    [[ "${COMP_WORDS[@]}" == *"-"* ]] && comnum=2 || comnum=1;

    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    opts="--help --restart -h -r"

    if (( COMP_CWORD > comnum )); then
        COMPREPLY=( $(for filename in "/tmp/testdir/"*; do echo ${filename##*/}; done) )
        if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
            #
            # you can try COMPREPLY+=( zzz ) and see what's happening
            #
            COMPREPLY+=( ' ' )
        fi
        return
    fi

    if [[ ${cur} == -* ]]; then
        COMPREPLY=( $( compgen -W "${opts}" -- ${cur} ) )
        return 0
    else
        COMPREPLY=( $( for filelist in /tmp/testdir/"$2"*; do echo ${filelist##*/}; done ) )
    fi
}

complete -F _foo_complete foo.sh


更新:

是否可以将空字符串移动到完成的末尾,所以看起来没有空格?

如果您可以自己对完成候选进行排序,则可以使用complete -o nosort (需要 Bash 4.4+)

【讨论】:

  • 您好,效果很好。如果要问的不是太多,如何使响应/可能的完成与左对齐而不是居中对齐?谢谢
  • 没有什么是完美的。足够好就足够了。
  • 那么是否可以将空字符串移动到完成的末尾,所以看起来没有空格?我一直在寻找那个
  • 更新了答案。
【解决方案3】:

根据您的完整要求编辑的代码:

_test(){
   COMPREPLY=(                                                                                                                      
      $( [[ "$2" == -* ]] && compgen -W'-a -ab -c' -- "$2" \                                                                
      || [[ -d test ]] && compgen -W'$(basename -a test/*)' -- "$2" | grep -v -w -f <(printf "%s\n" "${COMP_WORDS[@    ]}:1:((COMP_CWORD-1))"))
   )
}

if the test directory doesn't exist completion would work with current directory contents so we have to check it

options can be used multiple times as you didn't specify it. 

1 2 完成 -F _test 测试

【讨论】:

  • 1.是的,参数上没有重复的文件/选项; 2. 也可以,如果用户希望包含选项; 3. 按下tab时应显示当前目录中的文件。
  • 把需求弄对很重要,不然就是胡闹,1和3缺席,2定义不够;当用户使用“-”开始参数时,2 是否应该处于活动状态,或者这些选项是否应该与当前目录和特定目录中可能的文件一起列出,而不是重复?选项应该首先列出还是像默认完成一样排序?
  • 如果用户以- 开始参数,它应该带参数--restart --help 而不列出可能的文件,如果- 不存在 - 然后列出所有(没有重复/重复)可能的文件。这意味着如果用户已经选择了file1,那么在下一个选项中他将不再选择。
  • 只是为了 100% 确定,当您从“当前目录”写入文件时,您的意思是从当前目录加上 test/ 目录,只有前者?只有后者?
  • 后者,仅来自测试目录
猜你喜欢
  • 1970-01-01
  • 2011-04-26
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
  • 1970-01-01
相关资源
最近更新 更多