我正在处理大量文件,并将它们保存在一个目录中。每次我进入该目录并意外按Tab两次时,都会花费太长时间(可能超过一分钟)才能显示与模式匹配的文件,并且我对这种行为感到非常恼火。
比如我的目录结构是:
my-project/
├── docs/
├── data/ <---- contains around 200k files.
└── analyser/
由于我仍然喜欢完成,是否有办法仅在data/
目录中禁用此功能?比如设置超时 5 秒,或者在特定目录内自动关闭完成的脚本?
答案1
这并不完美,但 bash 完成又是一件相当棘手的事情......
最简单的方法是基于每个命令,它比 稍微灵活一些FIGNORE
,您可以这样做:
complete -f -X "/myproject/data/*" vi
这指示自动完成功能vi
是针对文件完成的,并删除与-X
过滤器匹配的模式。缺点是模式没有标准化,因此../data
变化不会匹配。
下一个最好的事情可能是自定义PROMPT_COMMAND
函数:
# associative arrays of non-autocomplete directories
declare -A noacdirs=([/myproject/data]=1 )
function _myprompt {
[[ -n "${noacdirs[$PWD]}" ]] && {
echo autocomplete off
bind 'set disable-completion on'
} || {
echo autocomplete on
bind 'set disable-completion off'
}
}
PROMPT_COMMAND=_myprompt
这禁用完成(完全)当您在目录中时,但它对每个路径禁用它,而不仅仅是该目录中的文件。
对于定义的路径有选择地禁用此功能会更普遍,但我相信唯一的方法是使用默认的完成函数(bash-4.1 和更高版本complete -D
)和很多混乱。
这应该为你工作,但它可能有意想不到的副作用(即在某些情况下改变预期完成):
declare -A noacdirs=([/myproject/data]=1 )
_xcomplete() {
local cur=${COMP_WORDS[COMP_CWORD]} # the current token
name=$(readlink -f "${cur:-./}") # poor man's path canonify
dirname=$(dirname "$name/.")
[[ -n "${noacdirs[$dirname]}" ]] && {
COMPREPLY=( "" ) # dummy to prevent completion
return
}
# let default kick in
COMPREPLY=()
}
complete -o bashdefault -o default -F _xcomplete vi
这适用于完成vi
,可以根据需要添加其他命令。无论路径或工作目录如何,它都应该停止指定目录中文件的完成。
我相信一般方法complete -D
是在遇到每个命令时动态添加完成函数。可能还需要添加complete -E
(当输入缓冲区为空时完成命令名称)。
更新
这是 和 完成功能解决方案的混合版本PROMPT_COMMAND
,我认为它更容易理解和破解:
declare -A noacdirs=([/myproject/data]=1 [/project2/bigdata]=1)
_xcomplete() {
local cmd=${COMP_WORDS[0]}
local cur=${COMP_WORDS[COMP_CWORD]} # the current token
[[ -z "$cur" && -n "$nocomplete" ]] && {
printf "\n(restricted completion for $cmd in $nocomplete)\n"
printf "$PS2 $COMP_LINE"
COMPREPLY=( "" ) # dummy to prevent completion
return
}
COMPREPLY=() # let default kick in
}
function _myprompt {
nocomplete=
# uncomment next line for hard-coded list of directories
[[ -n "${noacdirs[$PWD]}" ]] && nocomplete=$PWD
# uncomment next line for per-directory ".noautocomplete"
# [[ -f ./.noautocomplete ]] && nocomplete=$PWD
# uncomment next line for size-based guessing of large directories
# [[ $(stat -c %s .) -gt 512*1024 ]] && nocomplete=$PWD
}
PROMPT_COMMAND=_myprompt
complete -o bashdefault -o default -F _xcomplete vi cp scp diff
nocomplete
当您进入配置的目录之一时,此提示功能会设置变量。仅当该变量非空时,修改后的完成行为才会生效和仅当您尝试从空字符串完成时,从而允许完成部分名称(删除条件-z "$cur"
以完全阻止完成)。注释掉这两printf
行以进行静默操作。
其他选项包括每个目录的.noautocomplete
标志文件,您可以touch
根据需要将其放在目录中;并使用 GNU 猜测目录大小stat
。您可以使用这三个选项中的任何一个或全部。
(stat
方法只是一个猜测,报告的目录大小随其内容而增长,这是一个“高水位线”,当文件在没有某些管理干预的情况下被删除时,通常不会缩小。它比确定可能很大的目录的实际内容要便宜。每个文件的精确行为和增量取决于底层文件系统。我发现它至少在 linux ext2/3/4 系统上是一个可靠的指标。)
即使返回空补全,bash 也会添加额外的空格(这只在行尾补全时才会发生)。您可以添加-o nospace
到命令中complete
来防止这种情况发生。
剩下的一个小问题是,如果您将光标退回到标记的开头并点击 Tab,默认完成将再次启动。将其视为一个功能;-)
(或者,如果您喜欢过度设计,您可以随意使用${COMP_LINE:$COMP_POINT-1:1}
,但我发现当您备份并尝试在命令中间完成时,bash 本身无法可靠地设置完成变量。)
答案2
如果您的data
目录包含具有特定后缀的文件,例如 .out
,然后您可以将bash
变量设置FIGNORE
为".out"
,这些将被忽略。尽管也可以使用目录名称,但这对当前工作目录名称没有帮助。
例子:
在具有 raid 1 HDD 的物理主机上创建数千个 100KB 测试文件:
for i in {1..200000}; do dd if=/dev/urandom bs=102400 count=1 of=file$i.json; done
设置bash
变量:
$ FIGNORE=".json"
创建测试文件:
$ touch test.out
测试于5,000文件:
$ ls *.json|wc -l
5587
$ vi test.out
vi 和 single tab
before之间不会test.out
出现任何延迟。
测试于50,000文件:
$ ls *.json|wc -l
51854
$ vi test.out
单个选项卡在出现之前会产生几分之一秒的延迟test.out
。
测试于20万文件:
$ ls *.json|wc -l
bash: /bin/ls: Argument list too long
$ ls | awk 'BEGIN {count=0} /.*.json/ {count++} END {print count}'
200000
$ vi test.out
vi 和 single 之间出现 1 秒的tab
延迟test.out
。
参考:
摘自 man bash
FIGNORE
A colon-separated list of suffixes to ignore when performing filename completion (see READLINE below). A filename whose suffix matches one of the entries in FIGNORE is
excluded from the list of matched filenames. A sample value is ".o:~".
答案3
猜猜这就是你想要的(从@mr.spuratic借用了一些代码)
请注意,这不会阻止您使用完整路径按 TAB 键(例如vim /directory/contains/lots/files<tab><tab>
function cd() {
builtin cd "$@"
local NOCOMPLETION=( "/" "/lib" )
local IS_NOCOMPLETION=0
for dir in "${NOCOMPLETION[@]}"
do
echo $dir
if [[ $(pwd) == "$dir" ]]
then
echo "disable completion"
bind "set disable-completion on"
IS_NOCOMPLETION=1
return
fi
done
if [[ $IS_NOCOMPLETION -eq 0 ]]
then
echo "enable completion"
bind "set disable-completion off"
fi
}