我想从自定义完成中获得以下行为
鉴于
$ 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
这对我没什么帮助。
我是否需要对输出进行后处理,或者是否有一些神奇的选项组合可以使用?
答案 0 :(得分:16)
我遇到了同样的问题。这是我正在使用的解决方法:
-o default
注册完成功能,例如complete -o default -F _my_completion
。COMPREPLY=()
并让Readline接管(这就是-o default
所做的事情。)这有一个潜在的问题 - 您可能正在使用COMPREPLY=()
拒绝在不合适的情况下完成,现在它将不再起作用。在Bash 4.0及更高版本中,您可以使用compopt
解决此问题,如下所示:
compopt +o default
。当COMPREPLY
为空时,这将禁用Readline文件名完成。compopt -o default; COMPREPLY=()
。换句话说,只在您需要时启用Readline文件名完成。我还没有找到4.0之前Bash的完整解决方法,但我有一些适合我的东西。如果有人真正关心的话,我可以形容这一点,但希望这些旧的Bash版本很快就会失去常用。
答案 1 :(得分:5)
此行为受 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
在此处找到了在当前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>
,则有两种可能的完成: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
表示显示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.
注意:compgen
和complete
具有相同的参数。
答案 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
,则两个选项卡就足以输入它们了!