在查找文件时,将Compgen在目录上包含斜线



我想从我的自定义完成

获得以下行为

给定

$ 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对我无济于事。

我需要后处理输出,还是有效的选项有一些魔术组合?。这是我正在使用的解决方法:

  1. -o default注册完成功能,例如complete -o default -F _my_completion
  2. 当您要完成文件名时,只需设置COMPREPLY=()并让Readline接管(这就是-o default所做的)。

有一个潜在的问题 - 您可能会使用COMPREPLY=()拒绝在不合适的地方完成完成,现在它将不再起作用了。在Bash 4.0及更高版本中,您可以使用compopt进行以下操作:

  1. 在完成功能的顶部,始终运行compopt +o default。当COMPREPLY为空时,该禁用读取线文件名完成。
  2. 当您要完成文件名时,请使用compopt -o default; COMPREPLY=()。换句话说,只有在需要时才能完成readline文件名完成。

我还没有找到4.0前狂欢的完整解决方法,但是我的功能对我来说足够好。如果有人真的在乎,我可以描述这一点,但是希望这些较旧的狂欢版本无论如何都会很快从共同的使用中掉出来。

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


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

compgen -f -- $cur

其中 $cur是您在交互式选项卡完成期间输入的单词。

如果我在具有文件afile.py和子目录adircur=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表示与file1中的任何模式匹配的文件2中的行。
  • -F表示File1中的模式必须与(作为子字符串)完全匹配;他们不是正则表达式。
  • -v表示颠倒匹配:仅显示中的线。
  • <(...)是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意味着您在目录的/之后没有获得空间。对于普通文件,我们在结尾处添加了一个空间。

在单独的功能中使用此功能的一件好事是它易于测试!例如,这是一个自动测试:

diff -u <(printf "afile.py nadir/n") <(_mycmd_compgen_filenames "a") 
    || { echo "error: unexpected completions"; exit 1; }

此行为受 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

在此处找到当前外壳中的读取线变量的提示:https://unix.stackexchange.com/a/27545。我没有确定如何测试读取变量的当前值。

其他想法

也许只有学术利益...

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

compgen -d -S / f

使用传递给complete指令的-o filenames选项。只要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 comp-option ...

     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具有相同的参数。

这对我有用:

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

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

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

首先在答案中添加尾随的斜线。然后运行 compopt -o nospace命令以删除后续空间。现在,如果您的目录中只有foo/bar,则需要两个选项卡即可输入!

相关内容

  • 没有找到相关文章