Try*_*tøl 20 bash autocomplete bash-completion
我想从我的自定义完成中获得以下行为
特定
$ mkdir foo
$ touch foo faz/bar faz/baz
Run Code Online (Sandbox Code Playgroud)
我想得到这个
$ foo -u <tab><tab> =>
foo faz/
$ foo -u fa<tab><tab> =>
foo -u faz/
$ foo -u faz/<tab><tab> =>
bar baz
Run Code Online (Sandbox Code Playgroud)
我认为compgen -f f
会输出foo faz/
,但它的输出foo faz
对我没什么帮助.
我是否需要对输出进行后处理,或者是否有一些神奇的选项组合可以使用?
jjl*_*lin 18
我遇到了同样的问题.这是我正在使用的解决方法:
-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版本很快就会失去常用.
以前的答案需要 bash 4,或者不能与同一命令的其他基于 compgen 的完成组合。以下解决方案不需要 compopt(因此它适用于 bash 3.2)并且它是可组合的(例如,您可以轻松添加额外的过滤以仅匹配某些文件扩展名)。如果不耐烦,请跳到底部。
您可以通过直接在命令行上运行来测试 compgen:
compgen -f -- $cur
Run Code Online (Sandbox Code Playgroud)
$cur
到目前为止,您在交互式选项卡完成期间输入的单词在哪里。
如果我在一个包含文件afile.py
和子目录的目录中adir
,cur=a
那么上面的命令将显示以下内容:
$ cur=a
$ compgen -f -- $cur
adir
afile
Run Code Online (Sandbox Code Playgroud)
请注意,compgen -f
显示文件和目录。compgen -d
仅显示目录:
$ compgen -d -- $cur
adir
Run Code Online (Sandbox Code Playgroud)
添加-S /
将为每个结果添加一个斜杠:
$ compgen -d -S / -- $cur
adir/
Run Code Online (Sandbox Code Playgroud)
现在,我们可以尝试列出所有文件和所有目录:
$ compgen -f -- $cur; compgen -d -S / -- $cur
adir
afile.py
adir/
Run Code Online (Sandbox Code Playgroud)
请注意,没有什么能阻止您多次调用 compgen!在您的完成脚本中,您将像这样使用它:
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -f -- "$cur"; compgen -d -S / -- "$cur") )
Run Code Online (Sandbox Code Playgroud)
不幸的是,这仍然没有给我们想要的行为,因为如果你输入,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
Run Code Online (Sandbox Code Playgroud)
让我们分解一下:
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$
Run Code Online (Sandbox Code Playgroud)
我已经添加了开始和结束标记与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"
}
Run Code Online (Sandbox Code Playgroud)
你像这样使用它:
_mycmd_complete() {
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(_mycmd_compgen_filenames "$cur") )
}
complete -o nospace -F _mycmd_complete mycmd
Run Code Online (Sandbox Code Playgroud)
请注意,这-o nospace
意味着您在目录的/
. 对于普通文件,我们在 sed 末尾添加了一个空格。
将它放在一个单独的函数中的一个好处是它很容易测试!例如,这是一个自动化测试:
diff -u <(printf "afile.py \nadir/\n") <(_mycmd_compgen_filenames "a") \
|| { echo "error: unexpected completions"; exit 1; }
Run Code Online (Sandbox Code Playgroud)
此行为受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'
Run Code Online (Sandbox Code Playgroud)
要使更改对以后的调用永久bash
生效,通常将以下内容添加到INPUTRC文件中~/.inputrc
:
$if Bash
# append '/' to dirnames
set mark-directories on
$endif
Run Code Online (Sandbox Code Playgroud)
可以在以下位置找到在当前shell中设置readline变量的提示:https : //unix.stackexchange.com/a/27545。我不确定如何测试readline变量的当前值。
也许只有学术兴趣...
仅创建目录名称列表并添加斜杠:-
compgen -d -S / f
Run Code Online (Sandbox Code Playgroud)