是否可以为菜单完成定义完成规范?

Pie*_*rre 1 bash bash-completion

我有一个~/builds包含以下子目录的文件夹:

> tree ~/builds
~/builds
??? projectA
?   ??? build_2020_04_10_ok
?   ??? build_2020_04_11_ko
?   ??? build_2020_04_12_ok
?   ??? ...
??? projectB
    ??? build_2020_04_10_ok
    ??? build_2020_04_11_ok
    ??? build_2020_04_12_ok
    ??? ...
Run Code Online (Sandbox Code Playgroud)

目前,当我编写一个将子文件夹之一作为参数的命令并使用自动完成功能时,会bash列出所有候选:

> mycmd ~/builds/projectA/[TAB]
> mycmd ~/builds/projectA/build_2020_04_1[TAB][TAB]
build_2020_04_10_ok build_2020_04_11_ko build_2020_04_12_ok ...
Run Code Online (Sandbox Code Playgroud)

那不是我想要的行为。

我想要的更像是类似 Windows 的自动完成。我知道我可以通过修改我的.bashrc方式来使用它:

bind '"\C-g": menu-complete'
Run Code Online (Sandbox Code Playgroud)

现在,当我按下时会发生以下情况Ctrl+g

> mycmd ~/builds/projectA/[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_10_ok[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_11_ko[Ctrl+g]
> mycmd ~/builds/projectA/build_2020_04_12_ok
Run Code Online (Sandbox Code Playgroud)

这几乎就是我想要的。我想更改子文件夹出现的顺序(首先是最新的,然后是旧的)。另外,我想丢弃所有ko构建。换句话说,我想要这个命令定义的顺序:

ls -dr build*ok
build_2020_04_12_ok/  build_2020_04_10_ok/
Run Code Online (Sandbox Code Playgroud)

因此,我想定义一个完成规范,menu-complete以便仅在~/builds. 我看到有可能做到这一点complete,但我没有找到任何信息来做到这一点menu-complete

是否可以?

dim*_*414 5

正如@chepner 所说,menu-complete委托与完成相同的完成complete(参见man readline)。所以最初我认为不可能根据它是通过complete还是menu-complete. 但是我检查了是否根据调用的钩子设置了任何不同的值,并且有一些突出的东西:

# Get the current set of environment variables
# the sed expression strips functions to just print variables
# There's probably a better way to do this...
bash-5.0$ get_env() {
  set | sed -n '/^[^=]*$/q;p' | sort
}

# Define a function to use as a completion that simply prints the changes
# in the environment between the normal shell and from within a completion
bash-5.0$ diff_env() {
  echo # extra echo's for readability
  sdiff -s /tmp/env.txt <(get_env)
}
# Register it as a completion (the command doesn't actually need to exist)
bash-5.0$ complete -F diff_env foo

bash-5.0$ bind '"\C-g": menu-complete'

# Persist the initial environment
bash-5.0$ get_env > /tmp/env.txt

bash-5.0$ foo [TAB]
BASH_LINENO=([0]="12")            |    BASH_LINENO=([0]="1" [1]="13")
BASH_SOURCE=([0]="main")          |    BASH_SOURCE=([0]="main" [1]="main")
                                  >    COMP_CWORD=1
                                  >    COMP_KEY=9
                                  >    COMP_LINE='foo '
                                  >    COMP_POINT=4
                                  >    COMP_TYPE=9
                                  >    COMP_WORDS=([0]="foo" [1]="")
                                  >    _=echo
FUNCNAME=([0]="get_env")          |    FUNCNAME=([0]="get_env" [1]="diff_env")
_=get_env                         <

^C
bash-5.0$ foo [Ctrl+g]
BASH_LINENO=([0]="12")            |    BASH_LINENO=([0]="1" [1]="14")
BASH_SOURCE=([0]="main")          |    BASH_SOURCE=([0]="main" [1]="main")
                                  >    COMP_CWORD=1
                                  >    COMP_KEY=9
                                  >    COMP_LINE='foo '
                                  >    COMP_POINT=4
                                  >    COMP_TYPE=37
                                  >    COMP_WORDS=([0]="foo" [1]="")
                                  >    _=echo
FUNCNAME=([0]="get_env")          |    FUNCNAME=([0]="get_env" [1]="diff_env")
_=get_env                         <
^C
Run Code Online (Sandbox Code Playgroud)

发现了吗?COMP_TYPE是不同的!man bash解释它根据完成行为设置为整数;描述有点不透明,但值是给定完成类型的 ASCII 代码点(9 表示tab,37 表示%)。

所以我们可以定义一个完成函数来检查这个值以改变它的行为:

bash-5.0$ clever_complete() {
  case "$COMP_TYPE" in
    9) COMPREPLY=("tab!") ;;
    37) COMPREPLY=("ctrl-g!") ;;
    # should probably behave the same as [TAB], but I split it out to demonstrate
    *) COMPREPLY=("IDK... $COMP_TYPE") ;;
  esac
}
bash-5.0$ complete -F clever_complete bar
bash-5.0$ bar [TAB]tab! ^C
bash-5.0$ bar [Ctrl+g]ctrl-g! ^C
Run Code Online (Sandbox Code Playgroud)

希望这足以继续:) 如果您需要帮助实际编写complete具有您描述的行为的函数,我建议使用当前实现开始一个单独的问题(用于complete -p [command]查看当前完成,并type [function-name]查看完成的实现),它从那里调整现有行为应该很简单。