在查找文件时让compgen在目录中包含斜杠

时间:2012-10-17 11:33:44

标签: bash autocomplete bash-completion

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

鉴于

$ 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这对我没什么帮助。

我是否需要对输出进行后处理,或者是否有一些神奇的选项组合可以使用?

6 个答案:

答案 0 :(得分:16)

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

  1. 使用-o default注册完成功能,例如complete -o default -F _my_completion
  2. 如果您想要填写文件名,只需设置COMPREPLY=()并让Readline接管(这就是-o default所做的事情。)
  3. 这有一个潜在的问题 - 您可能正在使用COMPREPLY=()拒绝在不合适的情况下完成,现在它将不再起作用。在Bash 4.0及更高版本中,您可以使用compopt解决此问题,如下所示:

    1. 在完成功能的顶部,始终运行compopt +o default。当COMPREPLY为空时,这将禁用Readline文件名完成。
    2. 如果要填写文件名,请使用compopt -o default; COMPREPLY=()。换句话说,只在您需要时启用Readline文件名完成。
    3. 我还没有找到4.0之前Bash的完整解决方法,但我有一些适合我的东西。如果有人真正关心的话,我可以形容这一点,但希望这些旧的Bash版本很快就会失去常用。

答案 1 :(得分: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

答案 2 :(得分:4)

以前的答案需要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 process substitution。它允许您在预期文件的位置运行任何命令。

所以我们告诉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; }

答案 3 :(得分:1)

使用传递给-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具有相同的参数。

答案 4 :(得分:0)

这对我有用:

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

complete -F _my_completion foo

答案 5 :(得分:0)

如果您希望在目录完成后继续使用标签页;这是完整的解决方案:

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

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