如何正确地使自定义 zsh 完成“正常工作”?

gro*_*taj 5 ubuntu zsh autocomplete oh-my-zsh

如果我在这里做了一些愚蠢的事情,请原谅我,文档很大而且搜索还没有找到任何东西。

我正在尝试为我的自定义脚本创建 shell 完成fab。对于 bash 来说,这很容易,只需将它们放入即可/etc/bash_completion.d工作。但是,天哪,zsh 是 PITA 吗...

我有完成功能_fab,当使用compdef _fab fab. 我把它放在/usr/share/zsh/vendor-completions/_fab已经在我的$fpath. 该文件#compdef fabcompdef _fab fab. 看起来挺好的:

$ type _fab
_fab is an autoload shell function
Run Code Online (Sandbox Code Playgroud)

但是每当我启动一个新的 shell 时,fab补全都不起作用(来自 的其他函数vendor-completions,如_docker,很好)。compinit针对该特定外壳修复了此问题。我发现rm ~/.zcompdump ~/.zcompdump-$(hostname)-5.1.1; compinit它可以永久工作(5.1.1 = 我的 zsh 版本)。

问题:

  1. 什么以及何时读取~/.zcompdump以设置初始完成?
  2. man zshall 说:

    下一次调用 compinit 将读取转储文件而不是执行完全初始化。

    如果是这样,compinit在我删除之前不会修复我的完成~/.zcompdump,对吗?我错过了什么吗?

  3. ~/.zcompdump-$(hostname)-5.1.1它与什么以及如何相关.zcompdump?唯一的区别是在~/.oh-my-zsh/completions(因为$ZSH指向~/.oh-my-zsh) 中的一个完成。这是 oh-my-zsh 的事情吗?
  4. 如果我要将这些补全打包到一个可再分发的包中或创建一个安装程序脚本,我应该在哪里放置 zsh 补全以及在安装过程中我还应该做什么以确保一切正常?

我的目标是 Ubuntu 16.04、18.04 和 19.04,但欢迎提供非发行版特定信息。我正在使用 zsh 5.1.1 和最近的 oh-my-zsh 在 Ubuntu 16.04 上对此进行测试。

Gil*_*il' 5

TL、DR:在正常操作中,只需将文件放入适当的目录即可。测试时,需要删除缓存文件(.zcompdump默认情况下,但用户可以将其放在不同的位置,oh-my-zsh 确实将其放在不同的位置)。


简单的答案是在第一行是#compdef fab. 该文件必须位于 上的目录中$fpath

该文件可以包含函数体,也可以包含函数的定义,然后是对函数的调用。也就是说,要么文件包含类似

#compdef fab
_arguments …
Run Code Online (Sandbox Code Playgroud)

或者

#compdef fab
function _fab {
  _arguments …
}
_fab "$@"
Run Code Online (Sandbox Code Playgroud)

该文件必须$fpathcompinit运行前存在。这意味着您需要注意事物的顺序.zshrc:首先将任何自定义目录添加到$fpath,然后调用compinit. 如果您使用诸如 oh-my-zsh 之类的框架,请确保$fpath在 oh-my-zsh 代码之前添加任何自定义目录。

compinit是初始化完成系统的函数。它读取所有文件$fpath并检查它们的第一行是否有魔术指令#autoload#compdef.

.zcompdump是使用的缓存文件compinit~/.zcompdump是默认位置;跑步时可以选择不同的位置compinit。Oh-my-zsh 调用时compinit可以-d选择使用变量指定的不同缓存文件名ZSH_COMPDUMP默认为

ZSH_COMPDUMP="${ZDOTDIR:-${HOME}}/.zcompdump-${SHORT_HOST}-${ZSH_VERSION}"
Run Code Online (Sandbox Code Playgroud)

包含主机名是为了那些在机器之间共享主目录并且可能在不同机器上安装不同软件的人。包含 zsh 版本是因为缓存文件在版本之间不兼容(它包含从版本到版本更改的代码)。

我认为您的所有问题都是由于陈旧的缓存文件造成的(这使您的情况过于复杂)。不幸的是,zsh 确定缓存文件是否陈旧的算法并不完美,大概是为了速度。它不检查 上文件的内容或时间戳$fpath,它只是对它们进行计数。一个.zcompdump文件以这样的行开头

#files: 858     version: 5.1.1
Run Code Online (Sandbox Code Playgroud)

如果zsh版本和文件数正确,zsh加载缓存文件。

缓存文件只包含命令名称之间的关联,不包含完成函数的代码。以下是缓存透明工作的一些常见场景:

  • 如果向 中添加新文件$fpath,则缓存无效。
  • 更一般地,如果您在 上添加和删除文件$fpath,并且删除的文件总数与删除的文件总数不同,这会使缓存无效。
  • 如果您将文件移动到不同的目录$fpath而不更改其名称,这不会影响缓存中的任何内容,因此缓存保持正确。
  • 如果在$fpath不更改文件第一行的情况下修改文件,这不会影响缓存中的任何内容,因此缓存保持正确。

下面是一些缓存失效的常见场景,但 zsh 没有意识到。

  • 您将一些文件添加到$fpath和删除的文件数量完全相同。
  • 您将文件重命名为$fpath.
  • 您添加或修改文件顶部的#compdef(或#autoload) 行。

最后一点是在测试过程中往往会咬人的东西。如果更改该#compdef行,则需要删除该.zcompdump文件并重新启动 zsh(或重新运行compinit)。

如果您将完成放在可再发行包中,只需将完成文件放入系统范围内的$fpath. 对于 Ubuntu 包,合适的位置是/usr/share/zsh/vendor-completions. 对于安装在 下的东西/usr/local,那就是/usr/local/share/zsh/site-functions. 这就是你需要做的。

不透明的一件事是,如果您需要#compdef在升级中更改行,或者您删除或重命名某些文件。在这种情况下,用户将需要删除他们的缓存文件,而这不是您可以从安装在多用户计算机上的包中执行的操作。