mattmc3/zsh_unplugged

GitHub: mattmc3/zsh_unplugged

zsh_unplugged 用一个极简的 shell 函数替代传统 Zsh 插件管理器,让用户无需依赖臃肿的第三方工具即可轻松管理 Zsh 插件。

Stars: 498 | Forks: 22

# zsh_unplugged TLDR;你不需要一个庞大臃肿的插件管理器来管理 Zsh 插件。一个简单的约 20 行的函数可能就足够了。 点击这里[跳转到代码](#jigsaw-the-humble-plugin-load-function)。 ## :electric_plug: Zsh 插件管理器 ### :newspaper_roll: 当前状况 目前 Zsh 插件管理器的数量多得令人尴尬。其中许多已经停止维护、不再积极开发、是还没有多少用户的新项目,或者除了作为新奇事物外几乎没有存在的理由。 以下是来自 [awesome-zsh-plugins] 的许多(但肯定不是全部)插件管理器列表: | Zsh 插件管理器 | 性能 | 当前状态 | | --- | --- | --- | | [antibody] | :rabbit2: 快 | :imp: 维护模式,无新功能 | | [antigen] | :turtle: 慢 | :imp: 维护模式,无新功能 | | [antidote] | :rabbit2: 快 | :white_check_mark: 活跃 | | [sheldon] | :question: 未知 | :white_check_mark: 活跃 | | [zcomet] | :rabbit2: 快 | :white_check_mark: 活跃 | | [zgem] | :question: 未知 | :skull_and_crossbones: 已废弃 | | [zgen] | :rabbit2: 快 | :skull_and_crossbones: 已废弃 | | [zgenom] | :rabbit2: 快 | :white_check_mark: 活跃 | | [zinit-continuum] | :rabbit2: 快 | :white_check_mark: 活跃 [\*][#1] | | [zinit] | :rabbit2: 快 | :cursing_face: 作者删除了项目 | | [zit] | :question: 未知 | :imp: 近期提交很少/没有 | | [znap] | :rabbit2: 快 | :white_check_mark: 活跃 | | [zplug] | :turtle: 慢 | :skull_and_crossbones: 已废弃 | | [zplugin][zinit] | :rabbit2: 快 | :cursing_face: 更名为 zinit,作者已删除 | | [zpm] | :rabbit2: 快 | :white_check_mark: 活跃 | | [zr] | :rabbit2: 快 | :imp: 已完成,欢迎修复 bug | _完全公开,我是其中一个的作者 - [antidote](之前叫 [pz])。_ 新的插件管理器也层出不穷: | Zsh 插件管理器 | 性能 | 当前状态 | | --- | --- | --- | | [mzpm] | :question: 未知 | :hatching_chick: 新 | | [tzpm] | :question: 未知 | :hatching_chick: 新 | | [uz] | :question: 未知 | :hatching_chick: 新 | | [zed] | :question: 未知 | :hatching_chick: 新 | ### :firecracker: 导火索 2021 年 1 月,我正在使用的插件管理器 [antibody] 被弃用了。 作者甚至[说过这样的话](https://github.com/getantibody/antibody/tree/2ca7616ae78754c0ab70790229f5d19be42206e9): 在那之前,我使用的是 [zgen],它也停止了积极开发,[开发者](https://github.com/tarjoilija)似乎已经消失了。(*感谢 @jandamm 继续维护 Zgen 的 [Zgenom](https://github.com/jandamm/zgenom)!*) 2021 年 11 月,一个相对知名且流行的 Zsh 插件管理器 zinit 在没有任何警告的情况下从 GitHub 上完全删除。事实上,作者[几乎删除了他的所有作品][zdharma-debacle]。Zinit 非常受欢迎,因为它速度超快,作者多年来在多个场合推广他的项目。(*感谢 [zdharma-continuum] 继续维护 zinit!*) 鉴于 Zsh 插件管理器领域的不稳定性,我开始思考为什么我还要使用插件管理器。 ### :bulb: 简单的想法 在 [antibody] 被弃用后,我尝试了 [znap],但当时它还在早期开发阶段,经常崩溃,所以像之前的许多人一样,我决定自己写一个 - [antidote]。 在开发 [antidote] 时,我的目标很简单 - 做一个快速、实用且易于理解的插件管理器 - 这正是 [zgen] 和 [antibody] 的优点。虽然 [antidote] 是一个很好的项目,如果你想用插件管理器我完全推荐它,但我一直在想,我是否可以进一步精简到一个单一的*函数*,看看不用插件管理工具会是什么样子。 于是诞生了……**zsh_unplugged**。 这不是一个插件管理器 - 它是一种向你展示如何使用简短、易于理解的 Zsh 代码片段来管理自己插件的方式。这一切的想法是,也许我们可以一劳永逸地揭开插件管理器的神秘面纱。对于基本配置,我们可以完全不用插件管理器,自己动手搞定。 你可以拿一个约 20 行的函数,从这里开始你就拥有了管理自己插件所需的一切。作为对比,我对 zinit 项目运行了 [scc](https://github.com/boyter/scc),结果是数千行 Zsh 代码,以及数千行的支持项目文件。\*! ``` $ zinit_tmpdir=$(mktemp -d) $ git clone --depth 1 https://github.com/zdharma-continuum/zinit $zinit_tmpdir $ git -C $zinit_tmpdir rev-parse --short HEAD 1375adf8 $ scc --no-cocomo $zinit_tmpdir $ test -d $zinit_tmpdir && rm -rf -- $zinit_tmpdir ``` 结果: ``` ─────────────────────────────────────────────────────────────────────────────── Language Files Lines Blanks Comments Code Complexity ─────────────────────────────────────────────────────────────────────────────── YAML 16 643 84 78 481 0 Zsh 14 10971 937 1352 8682 1782 AsciiDoc 6 5375 1746 0 3629 0 Markdown 6 2251 603 0 1648 0 Shell 3 674 84 56 534 60 Makefile 2 111 27 12 72 7 SVG 2 683 2 2 679 0 Docker ignore 1 10 1 0 9 0 Dockerfile 1 41 9 2 30 8 JSON 1 47 0 0 47 0 License 1 22 4 0 18 0 gitignore 1 24 0 0 24 0 ─────────────────────────────────────────────────────────────────────────────── Total 54 20852 3497 1502 15853 1857 ─────────────────────────────────────────────────────────────────────────────── Processed 816700 bytes, 0.817 megabytes (SI) ─────────────────────────────────────────────────────────────────────────────── ``` \*_注:SLOC 在这里仅作为工作量、可维护性和复杂性的粗略对比_ ## :tada: 代码 ### :gear: 最原始的方式 如果你不想使用任何类似插件管理器的东西,你可以手动克隆并加载插件: ``` ZPLUGINDIR=$HOME/.zsh/plugins if [[ ! -d $ZPLUGINDIR/zsh-autosuggestions ]]; then git clone https://github.com/zsh-users/zsh-autosuggestions \ $ZPLUGINDIR/zsh-autosuggestions fi source $ZPLUGINDIR/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh if [[ ! -d $ZPLUGINDIR/zsh-history-substring-search ]]; then git clone https://github.com/zsh-users/zsh-history-substring-search \ $ZPLUGINDIR/zsh-history-substring-search fi source $ZPLUGINDIR/zsh-history-substring-search/zsh-history-substring-search.plugin.zsh if [[ ! -d $ZPLUGINDIR/z ]]; then git clone https://github.com/rupa/z \ $ZPLUGINDIR/z fi source $ZPLUGINDIR/z/z.sh ``` 这可能会变得相当重复、繁琐且难以维护。你需要找出每个插件的初始化文件,有时还需要将插件添加到你的 `fpath` 中。虽然这种方法可行,但还有另一种方式…… ### :jigsaw: 朴素的 `plugin-load` 函数 如果我们比手动调用 `git clone` 高一层抽象,我们可以使用一个简单的函数作为管理 Zsh 插件所需一切的基础: ``` ##? Clone a plugin, identify its init file, source it, and add it to your fpath. function plugin-load { local plugin repo commitsha plugdir initfile initfiles=() : ${ZPLUGINDIR:=${ZDOTDIR:-~/.config/zsh}/plugins} for plugin in $@; do repo="$plugin" clone_args=(-q --depth 1 --recursive --shallow-submodules) # Pin repo to a specific commit sha if provided if [[ "$plugin" == *'@'* ]]; then repo="${plugin%@*}" commitsha="${plugin#*@}" clone_args+=(--no-checkout) fi plugdir=$ZPLUGINDIR/${repo:t} initfile=$plugdir/${repo:t}.plugin.zsh if [[ ! -d $plugdir ]]; then echo "Cloning $repo..." git clone "${clone_args[@]}" https://github.com/$repo $plugdir if [[ -n "$commitsha" ]]; then git -C $plugdir fetch -q origin "$commitsha" git -C $plugdir checkout -q "$commitsha" fi fi if [[ ! -e $initfile ]]; then initfiles=($plugdir/*.{plugin.zsh,zsh-theme,zsh,sh}(N)) (( $#initfiles )) || { echo >&2 "No init file found '$repo'." && continue } ln -sf $initfiles[1] $initfile fi fpath+=$plugdir (( $+functions[zsh-defer] )) && zsh-defer . $initfile || . $initfile done } ``` 就这样。约 30 行代码,你就拥有了一个简单、健壮的 Zsh 插件管理替代方案,速度可能与大多数其他方案一样快。 它所做的就是克隆一个 Zsh 插件的 git 仓库,然后检查该仓库中是否有合适的 .zsh 文件作为初始化脚本使用。然后我们找到插件的 init 文件并在必要时创建符号链接,这使我们能够获得接近静态加载的性能优势,而不必每次打开新终端时都搜索要加载的插件文件。 然后,插件被加载并添加到 `fpath` 中。 如果你真的需要每一丝性能,你甚至可以获得涡轮增压超音速加载速度魔法 :rocket:。 [点击这里查看方法](#question-how-do-i-load-my-plugins-with-hypersonic-speed-rocket)。 ### :question: 如何在自己的 Zsh 配置中使用这个? 你可以自由地将上面的 `plugin-load` 函数直接放入你的 .zshrc 中,自己维护它,再也不用依赖其他人的插件管理器。或者,如果你愿意,这个仓库将 plugin-load 函数本身作为一个插件提供。 这是一个 .zshrc 示例: ``` # 你想把插件存储在哪里? ZPLUGINDIR=${ZPLUGINDIR:-${ZDOTDIR:-$HOME/.config/zsh}/plugins} # 获取 zsh_unplugged 并将其与其他插件存储在一起 if [[ ! -d $ZPLUGINDIR/zsh_unplugged ]]; then git clone --quiet https://github.com/mattmc3/zsh_unplugged $ZPLUGINDIR/zsh_unplugged fi source $ZPLUGINDIR/zsh_unplugged/zsh_unplugged.zsh # 制作你使用的 Zsh 插件列表 repos=( # prompts 'sindresorhus/pure' # regular plugins 'zsh-users/zsh-completions' 'ajeetdsouza/zoxide' # ... # You can pin plugins to a particular SHA 'zsh-users/zsh-syntax-highlighting@5eb677bb0fa9a3e60f0eff031dc13926e093df92' 'zsh-users/zsh-history-substring-search@87ce96b1862928d84b1afe7c173316614b30e301' 'zsh-users/zsh-autosuggestions@85919cd1ffa7d2d5412f6d3fe437ebdbeeec4fc5' ) # 现在加载你的插件 plugin-load $repos ``` 这是一个示例 [.zshrc](https://github.com/mattmc3/zsh_unplugged/blob/main/examples/zshrc.zsh)。 ### :question: 我可以用这个做一个微型 Zsh 插件管理器吗? 可以!这个项目使用 [unlicense](https://unlicense.org/)。你可以在任何地方随意使用这段代码。或者,如果你更喜欢使用已经构建好的、有支持的东西,这个项目在 [antidote.lite.zsh](antidote.lite.zsh) 文件中包含了自己的微型插件管理器实现。大约 100 行代码。 你可以在 [full_featured.zsh 示例文件](examples/full_featured.zsh) 中查看使用 zsh_unplugged 的完整功能示例。 ### :question: 如何更新我的插件? 更新插件就像删除 $ZPLUGINDIR 并重新加载 Zsh 一样简单。 ``` ZPLUGINDIR=~/.config/zsh/plugins rm -rfi $ZPLUGINDIR zsh ``` 如果你熟悉 `git` 命令,不想重建一切,你可以自己运行 `git pull`,甚至使用一个简单的 `plugin-update` 函数: ``` function plugin-update { ZPLUGINDIR=${ZPLUGINDIR:-$HOME/.config/zsh/plugins} for d in $ZPLUGINDIR/*/.git(/); do echo "Updating ${d:h:t}..." command git -C "${d:h}" pull --ff --recurse-submodules --depth 1 --rebase --autostash done } ``` ### :question: 如何列出我的插件? 你可以通过简单的 `ls` 命令查看已安装的插件: ``` ls $ZPLUGINDIR ``` 如果你需要更高级的功能,想查看插件的 git 来源,可以运行这个命令: ``` for d in $ZPLUGINDIR/*/.git; do git -C "${d:h}" remote get-url origin done ``` ### :question: 如何删除插件? 你可以直接从 .zshrc 的 `plugins` 列表中移除它。要完全删除,可以运行 `rm`: ``` # 移除 fast-syntax-highlighting 插件 rm -rfi $ZPLUGINDIR/fast-syntax-highlighting ``` ### :question: 如何以超音速 :rocket: 加载我的插件? 如果你选择使用 [romkatv/zsh-defer](https://github.com/romkatv/zsh-defer) 插件,你可以获得涡轮增压超音速加载速度魔法。本质上,如果你将 `romkatv/zsh-defer` 添加到你的插件列表中,之后加载的所有内容都会使用 zsh-defer,这意味着你将获得类似于 zinit 的 [turbo 模式](https://github.com/zdharma-continuum/zinit#turbo-and-lucid) 的速度。 值得注意的是,如果你喜欢在 Zsh 中使用类似 fish 的缩写的 [zsh-abbr] 插件,使用 zsh-defer [将大幅提升性能](https://github.com/olets/zsh-abbr/issues/52)。 :warning: 警告 - zsh-defer 的作者不推荐以这种方式使用该插件,所以要小心选择哪些插件用 zsh-defer 加载。如果某个插件出现异常行为,那么就在 zsh-defer 之前加载它。在我广泛的测试中,最大的性能提升只来自特别慢的插件,比如 [zsh-abbr]。 ### :question: 如果我想自定义插件的加载方式怎么办? 你可以将克隆和加载操作分成两个独立的函数,让你进一步自定义处理插件的方式。这种技术在使用像 [zsh-utils] 这样有嵌套插件的项目,或使用像 [zsh-bench] 这样不是插件的工具时特别有用。 ``` # 声明一个简单的 plugin-clone 函数,让用户自己 source 插件 function plugin-clone { local plugin repo commitsha plugdir initfile initfiles=() ZPLUGINDIR=${ZPLUGINDIR:-${ZDOTDIR:-$HOME/.config/zsh}/plugins} for plugin in $@; do repo="$plugin" clone_args=(-q --depth 1 --recursive --shallow-submodules) # Pin repo to a specific commit sha if provided if [[ "$plugin" == *'@'* ]]; then repo="${plugin%@*}" commitsha="${plugin#*@}" clone_args+=(--no-checkout) fi plugdir=$ZPLUGINDIR/${repo:t} initfile=$plugdir/${repo:t}.plugin.zsh if [[ ! -d $plugdir ]]; then echo "Cloning $repo..." git clone "${clone_args[@]}" https://github.com/$repo $plugdir if [[ -n "$commitsha" ]]; then git -C $plugdir fetch -q origin "$commitsha" git -C $plugdir checkout -q "$commitsha" fi fi if [[ ! -e $initfile ]]; then initfiles=($plugdir/*.{plugin.zsh,zsh-theme,zsh,sh}(N)) (( $#initfiles )) && ln -sf $initfiles[1] $initfile fi done } # 现在,plugin-source 是另一回事了 function plugin-source { local plugdir ZPLUGINDIR=${ZPLUGINDIR:-${ZDOTDIR:-$HOME/.config/zsh}/plugins} for plugdir in $@; do [[ $plugdir = /* ]] || plugdir=$ZPLUGINDIR/$plugdir fpath+=$plugdir local initfile=$plugdir/${plugdir:t}.plugin.zsh (( $+functions[zsh-defer] )) && zsh-defer . $initfile || . $initfile done } ``` 然后你可以像这样使用这两个函数: ``` # 制作一个 github 仓库列表 repos=( # not-sourcable (path only) plugins 'romkatv/zsh-bench' # projects with nested plugins 'belak/zsh-utils' 'ohmyzsh/ohmyzsh@a6beb0f5958e935d33b0edb6d4470c3d7c4e8917' # regular plugins 'zsh-users/zsh-completions' 'zsh-users/zsh-autosuggestions' 'zsh-users/zsh-history-substring-search@87ce96b1862928d84b1afe7c173316614b30e301' 'zdharma-continuum/fast-syntax-highlighting@3d574ccf48804b10dca52625df13da5edae7f553' ) plugin-clone $repos # zsh-bench 没有插件文件 # 它只需要添加到你的 $PATH 中 export PATH="$ZPLUGINDIR/zsh-bench:$PATH" # Oh-My-Zsh 插件依赖其 lib 目录中的内容 ZSH=$ZPLUGINDIR/ohmyzsh for _f in $ZSH/lib/*.zsh; do source $_f done unset _f # source 其他插件 plugins=( zsh-utils/history zsh-utils/completion zsh-utils/utility ohmyzsh/plugins/magic-enter ohmyzsh/plugins/history-substring-search ohmyzsh/plugins/z fast-syntax-highlighting zsh-autosuggestions ) plugin-source $plugins ``` 这是一个示例 [.zshrc](https://github.com/mattmc3/zsh_unplugged/blob/main/examples/zshrc_clone.zsh)。 ### :question: 如果我想让插件更快怎么办? 如果你是经验丰富的 Zsh 用户,你可能知道 [zcompile],它可以将你的 Zsh 脚本编译为字节码,从而可能加速它们。如果你确信自己在做什么,想从 Zsh 中榨取每一丝性能,可以使用这个函数: ``` function plugin-compile { ZPLUGINDIR=${ZPLUGINDIR:-$HOME/.config/zsh/plugins} autoload -U zrecompile local f for f in $ZPLUGINDIR/**/*.zsh{,-theme}(N); do zrecompile -pq "$f" done } ``` ### :question: 如何与 Oh-My-Zsh 或 Prezto 等 Zsh 框架一起使用? [Oh-My-Zsh][ohmyzsh] 和 [Prezto][prezto] 有自己的内置插件加载方式,只是它们没有提供克隆插件的方法。如果你使用这些框架,你不需要 zsh_unplugged 脚本。但是,你也不需要单独的插件管理器工具。以下是你自己处理克隆、在 Zsh 框架中摆脱插件管理器的方法: #### Oh-My-Zsh 如果你使用 [Oh-My-Zsh][ohmyzsh],不用插件管理器的方法是利用 `$ZSH_CUSTOM` 路径。 _注意,这假设你的 init 文件名为 {plugin_name}.plugin.zsh,但实际情况可能并非如此。_ ``` # .zshrc # 不要将这个列表命名为 'plugins',因为 omz 已经使用了该名称 repos=( marlonrichert/zsh-hist zsh-users/zsh-syntax-highlighting zsh-users/zsh-autosuggestions ) for repo in $repos; do if [[ ! -d $ZSH_CUSTOM/${repo:t} ]]; then git clone https://github.com/${repo} $ZSH_CUSTOM/plugins/${repo:t} fi done unset repo{s,} # 将你的外部插件添加到你的 OMZ 插件列表中 plugins=( ... zsh-hist zsh-autosuggestions ... zsh-syntax-highlighting ) ``` #### Prezto 如果你使用 [Prezto][prezto],不用插件管理器的方法是利用 `$ZPREZTODIR/contrib` 路径。 _注意,这假设你的 init 文件名为 {plugin_name}.plugin.zsh,但实际情况可能并非如此。_ ``` # .zshrc contribs=( rupa/z marlonrichert/zsh-hist mattmc3/zman ) for contrib in $contribs; do if [[ ! -d $ZPREZTODIR/contrib/${contrib:t} ]]; then git clone https://github.com/${contrib} $ZPREZTODIR/contrib/${contrib:t} fi done unset contrib{,s} # 将 contribs 添加到你的 `.zpreztorc` 中的 Prezto 模块列表中 zstyle ':prezto:load' pmodule \ ... \ z \ zsh-hist \ ... \ zman ```
标签:Cutter, DNS解析, dotfiles, Shell, shell脚本开发, shell自动加载, shell配置, SOC Prime, UNIX/Linux, Zsh, zsh_unplugged, Zsh插件, 工具比较, 开发工具, 开源项目, 性能优化, 技术博客, 插件管理, 效率工具, 极简主义, 检测绕过, 终端工具, 网络安全研究, 网络调试, 自动化, 软件开发, 轻量级工具