以前的答案需要 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进程替换。它允许您在预期文件的位置运行任何命令。
所以我们告诉 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; }