在 Bash 中对某些包含大量文件的目录禁用制表符补全?

在 Bash 中对某些包含大量文件的目录禁用制表符补全?

我正在处理大量文件,并将它们保存在一个目录中。每次我进入该目录并意外按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 tabbefore之间不会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
}  

相关内容