26

我想从我的自定义完成中获得以下行为

给定

$ mkdir foo
$ touch foo faz/bar faz/baz

我想得到这个

$ foo -u <tab><tab> =>
foo faz/

$ foo -u fa<tab><tab> =>
foo -u faz/

$ foo -u faz/<tab><tab> =>
bar baz

我认为那compgen -f f会输出foo faz/,但它输出foo faz对我没有多大帮助。

我是否需要对输出进行后处理,或者是否有一些神奇的选项组合来完成这项工作?

4

6 回答 6

24

我遇到了同样的问题。这是我正在使用的解决方法:

  1. 用 注册完成函数-o default,例如complete -o default -F _my_completion
  2. 当你想完成一个文件名时,只需设置COMPREPLY=()并让 Readline 接管(这就是它的-o default作用)。

这有一个潜在的问题——你可能会COMPREPLY=()在不合适的地方拒绝完成,现在这不再起作用了。在 Bash 4.0 及更高版本中,您可以使用compopt以下方法解决此问题:

  1. 在完成函数的顶部,始终运行compopt +o default. COMPREPLY这会在为空时禁用 Readline 文件名完成。
  2. 当您想要完成文件名时,请使用compopt -o default; COMPREPLY=(). 换句话说,仅在需要时启用 Readline 文件名完成。

我还没有为 4.0 之前的 Bash 找到完整的解决方法,但我有一些对我来说足够好的东西。如果有人真的关心,我可以描述这一点,但希望这些旧的 Bash 版本很快就会不再常用。

于 2013-09-28T03:35:46.520 回答
7

以前的答案需要 bash 4,或者不能与同一命令的其他基于 compgen 的完成组合。以下解决方案不需要 compopt(因此它适用于 bash 3.2)并且它是可组合的(例如,您可以轻松添加额外的过滤以仅匹配某些文件扩展名)。如果不耐烦,请跳到底部。


您可以通过直接在命令行上运行 compgen 来测试它:

compgen -f -- $cur

$cur到目前为止,您在交互式制表符完成过程中输入的单词在哪里。

如果我在一个有文件afile.py和子目录的目录adir中,cur=a那么上面的命令会显示以下内容:

$ cur=a
$ compgen -f -- $cur
adir
afile

注意compgen -f显示文件目录。compgen -d仅显示目录:

$ compgen -d -- $cur
adir

添加-S /将为每个结果添加一个斜杠:

$ compgen -d -S / -- $cur
adir/

现在,我们可以尝试列出所有文件和所有目录:

$ compgen -f -- $cur; compgen -d -S / -- $cur
adir
afile.py
adir/

请注意,没有什么能阻止您多次调用 compgen!在您的完成脚本中,您将像这样使用它:

cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -f -- "$cur"; compgen -d -S / -- "$cur") )

不幸的是,这仍然没有给我们想要的行为,因为如果你键入ad<TAB>你有两个可能的完成:adiradir/. 所以ad<TAB>将完成到adir,此时您仍然需要输入/来消除歧义。

我们现在需要的是一个返回所有文件但不返回目录的函数。这里是:

$ grep -v -F -f <(compgen -d -P '^' -S '$' -- "$cur") \
>     <(compgen -f -P '^' -S '$' -- "$cur") |
>     sed -e 's/^\^//' -e 's/\$$//'
afile.py

让我们分解一下:

  • grep -f file1 file2表示显示 file2 中与 file1 中的任何模式匹配的行。
  • -F表示 file1 中的模式必须完全匹配(作为子字符串);它们不是正则表达式。
  • -v表示反转匹配:仅显示不在file1 中的行。
  • <(...)是 bash进程替换。它允许您在预期文件的位置运行任何命令。

所以我们告诉 grep:这是一个文件列表——删除与这个目录列表匹配的任何文件。

$ grep -v -F -f <(compgen -d -P '^' -S '$' -- "$cur") \
>     <(compgen -f -P '^' -S '$' -- "$cur")
^afile.py$

我用 compgen 添加了开始和结束标记,-P ^因为-S '$'grep-F进行子字符串匹配,我们不想删除文件名a-file-with-adir-in-the-middle,因为它的中间部分匹配目录的名称。获得文件列表后,我们使用 sed 删除这些标记。

现在我们可以编写一个函数来做我们想做的事:

# Returns filenames and directories, appending a slash to directory names.
_mycmd_compgen_filenames() {
    local cur="$1"

    # Files, excluding directories:
    grep -v -F -f <(compgen -d -P ^ -S '$' -- "$cur") \
        <(compgen -f -P ^ -S '$' -- "$cur") |
        sed -e 's/^\^//' -e 's/\$$/ /'

    # Directories:
    compgen -d -S / -- "$cur"
}

你像这样使用它:

_mycmd_complete() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(_mycmd_compgen_filenames "$cur") )
}
complete -o nospace -F _mycmd_complete mycmd

请注意,这-o nospace意味着您在目录的/. 对于普通文件,我们用 sed 在末尾添加了一个空格。

将它放在一个单独的函数中的一个好处是它很容易测试!例如,这是一个自动化测试:

diff -u <(printf "afile.py \nadir/\n") <(_mycmd_compgen_filenames "a") \
    || { echo "error: unexpected completions"; exit 1; }
于 2016-10-24T20:45:05.380 回答
5

此行为受readline变量mark-directories(和相关的mark-symlinked-directories)的影响。我相信应该设置该变量以在目录名称complete打印一个斜杠(bash v3.00.16 中的默认值)。似乎相关的行为compgen不会在目录名称后附加斜杠:-\

将值mark-directories交替设置为onoff然后重试您的测试:-

bind 'set mark-directories on'
bind 'set mark-directories off'

要使更改永久用于将来的调用bash,请将以下内容添加到您的 INPUTRC 文件中,通常是~/.inputrc:-

$if Bash
# append '/' to dirnames
set mark-directories on
$endif

在当前 shell 中设置 readline 变量的提示可在此处找到:https ://unix.stackexchange.com/a/27545 。我没有确定如何测试 readline 变量的当前值。

其他想法

也许只是学术兴趣...

仅创建目录名称列表并附加斜杠:-

compgen -d -S / f
于 2012-10-17T17:02:17.480 回答
2

使用-o filenames传递给complete指令的选项。只要 的内容COMPREPLY是有效的路径,斜线就会被适当地附加,同时在完成非目录匹配后仍然添加一个空格。(适用于 bash 3.2。)

_cmd() {
  COMPREPLY=( $( compgen -f -- "$cur" ))
  COMPREPLY=( $( compgen -A file -- "$cur" ))  # same thing
}

complete -o filenames -F _cmd cmd

info -n "(bash)Programmable Completion Builtins"

完全的

... -o 组合选项 ...

     filenames
           Tell Readline that the compspec generates filenames, so
           it can perform any filename-specific processing (like
           adding a slash to directory names or suppressing
           trailing spaces).  This option is intended to be used
           with shell functions specified with `-F'.

和:

-A ACTION

... '文件'

           File names.  May also be specified as -f.

注意:compgencomplete具有相同的论点。

于 2019-08-07T00:17:10.507 回答
1

这对我有用:

_my_completion() { 
  compopt -o nospace 
  COMPREPLY=( $(compgen -S "/" -d "${COMP_WORDS[$COMP_CWORD]}") )
}

complete -F _my_completion foo
于 2014-11-14T10:57:45.153 回答
0

如果您希望在目录完成后能够继续使用选项卡;这是完整的解决方案:

cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -S "/" -d -- ${cur}) ); 
compopt -o nospace;

首先在答案中添加一个斜杠。然后运行compopt -o nospace命令以删除尾随空格。现在,如果您只有foo/bar在您的目录中,两个选项卡就足以输入它们!

于 2014-11-28T07:29:07.703 回答