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

Is it possible to define a completion specification for menu-complete?

我有一个文件夹 ~/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
    └── ...

目前,当我编写一个将子文件夹之一作为参数的命令并使用自动完成时,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 ...

这不是我想要的行为。

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

bind '"\C-g": menu-complete'

现在,当我按下 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

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

ls -dr build*ok
build_2020_04_12_ok/  build_2020_04_10_ok/

因此,我想为 menu-complete 定义一个完成规范,以便仅在 ~/builds 的子目录上执行我想要的行为。我看到可以为 complete 做这件事,但我在 menu-complete.

上找不到相关信息

可能吗?

正如@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

发现了吗? COMP_TYPE不一样! man bash 解释说它根据完成行为设置为一个整数;描述有点不透明,但值是给定类型的 ASCII 代码点(tab 为 9,% 为 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

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