我想从我的自定义完成
获得以下行为给定
$ 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
对我无济于事。
我需要后处理输出,还是有效的选项有一些魔术组合?。这是我正在使用的解决方法:
- 用
-o default
注册完成功能,例如complete -o default -F _my_completion
。 - 当您要完成文件名时,只需设置
COMPREPLY=()
并让Readline接管(这就是-o default
所做的)。
有一个潜在的问题 - 您可能会使用COMPREPLY=()
拒绝在不合适的地方完成完成,现在它将不再起作用了。在Bash 4.0及更高版本中,您可以使用compopt
进行以下操作:
- 在完成功能的顶部,始终运行
compopt +o default
。当COMPREPLY
为空时,该禁用读取线文件名完成。 - 当您要完成文件名时,请使用
compopt -o default; COMPREPLY=()
。换句话说,只有在需要时才能完成readline文件名完成。
我还没有找到4.0前狂欢的完整解决方法,但是我的功能对我来说足够好。如果有人真的在乎,我可以描述这一点,但是希望这些较旧的狂欢版本无论如何都会很快从共同的使用中掉出来。
以前的答案需要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>
,则可以完成两个可能的完成:adir
和adir/
。因此,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
的值交替设置为on
和off
,然后重试测试: -
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.
注意:compgen
和complete
具有相同的参数。
这对我有用:
_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
,则需要两个选项卡即可输入!