🐚

高速で設定しやすいZsh/BashプラグインマネージャーSheldonの紹介

2022/06/28に公開約10,600字

ZshのプラグインマネージャーというとAntigenzplugZinit(旧zplugin)などが挙げられます。

私はこれまでZinitを使ってきましたが、作者がGitHubのOrganizationを削除してしまうという事件が起きました。有志がローカルのクローンから復旧させてメンテンスが続いていますが、他のプラグインマネージャーへの乗り換えを検討していたところ、Sheldonを見つけました。

https://github.com/rossmacarthur/sheldon

SheldonはRust製のシェルプラグインマネージャーで、ZshだけでなくBashにも対応しています。ライセンスはApache License 2.0またはMIT Licenseを選択するデュアルライセンスで配布されています。

この記事ではmacOS上のZshでの利用を前提に記述していますが、他の環境でもインストール方法とシェルによる機能差分ぐらいしか違いはありません。

インストール

macOSではHomebrewでインストールすることができます。[1]

brew install sheldon

別のインストール方法を選択したい場合や、その他のOSの場合は📦 Installation - sheldon docsをご参照ください。

初期設定

設定ファイルの作成

次のコマンドでSheldonの設定ファイルを生成できます。

sheldon init --shell zsh

設定ファイルはシェルスクリプトではなく、TOML形式のファイルです。上記のコマンドを実行すると、~/.sheldon/plugins.tomlに生成されます。

XDG Base Directory Specificationにも対応していますが、いずれかの環境変数が定義されていないとXDG Base Directoryが使われない実装になっているため、環境変数未定義時にSpecificationのデフォルトのディレクトリが参照されない点にご注意ください。

シェル初期化時の設定

シェル起動時にSheldonをロードするため、~/.zshrcなどに次の1行を追加します。

eval "$(sheldon source)"

設定ファイルを生成した直後のsourceサブコマンドは何も出力しません。プラグインを追加するとそれに対応したコマンドが出力されるようになります。

また、sourceサブコマンドは~/.sheldon/plugins.lockを生成します。このロックファイルにインストールされたプラグインの内部的な情報が展開され、2回目以降はこの情報を元に実行することで高速に動作します。

Sheldonのロックファイルは一般的なパッケージマネージャーのロックファイルと性質が異なり、ローカル環境の状態を管理するものであるため、Git管理下に置いて他のマシンと共有するべきではありません。

設定ファイルの編集

プラグインの定義などは上記で作成したplugins.tomlで行います。直接パスを指定してエディターから開いてもよいですが、editサブコマンドを実行すると簡単に開くことができます。

sheldon edit

編集に用いるエディターを指定したい場合は環境変数EDITORを設定します。

EDITOR=vim sheldon edit

プラグインの追加

プラグインの追加はplugins.tomlを直接編集するか、addサブコマンドを使います。私はプラグインの追加時に設定を調整したりするため、plugins.tomlを直接編集してしまうことが多いです。

設定ファイルの編集による追加

プラグインはplugins.tomlpluginsテーブル配下に記述していきます。

例えば、zsh-users/zsh-autosuggestionsを追加する場合は次のように記述します。

plugins.toml
[plugins.zsh-autosuggestions]
github = "zsh-users/zsh-autosuggestions"

テーブル名のplugins.zsh-autosuggestionszsh-autosuggestionsの部分は一意なものを自分で命名します。ここで指定した名前はプラグインのリポジトリ内のどのファイルを読み込むのかに使われます。これはグローバルオプションのmatchキーの設定に基づいています。matchキーにはデフォルトで次の値が設定されており、順番にパターンとマッチしているかチェックして一番最初にマッチしたファイルを読み込みます。

match = [
    "{{ name }}.plugin.zsh",
    "{{ name }}.zsh",
    "{{ name }}.sh",
    "{{ name }}.zsh-theme",
    "*.plugin.zsh",
    "*.zsh",
    "*.sh",
    "*.zsh-theme"
]

この{{ name }}の部分にテーブル名で指定した名前が入るようになっています。もしファイル名と一致しない名前を定義したとしても、後続のワイルドカード指定のパターンに一致するため、特殊なケースでない限り問題ないですが、合わせておいた方がわかりやすいです。

そしてgithubキーで値にGitHubのOwner名を含んだリポジトリ名を指定します。

プラグインを追加するとsourceサブコマンドがプラグインを読み込むためのコマンドを出力するようになります。

seldon source
source "/Users/john/.sheldon/repos/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh"

コマンドによるプラグインの追加

同様のことをaddサブコマンドで行うには次のようにします。

sheldon add zsh-autosuggestions --github zsh-users/zsh-autosuggestions

addサブコマンドを実行するとplugins.tomlに指定されたプラグインが追加されます。addサブコマンドにはplugins.tomlのプラグインの設定で指定可能なキーをすべて引数として渡すことができます。

プラグインのバージョン固定

また、タグ(tag)やブランチ(branch)、コミットID(rev)を指定してバージョンを固定することもできます。

plugins.toml
[plugins.zsh-autosuggestions]
github = "zsh-users/zsh-autosuggestions"
tag = "v0.7.0"

プラグインの取得元の指定

他にも、プラグインの取得先としてGitリポジトリ(git)やGist(gist)、HTTP/HTTPS上のファイル(remote)、ローカルのファイル(local)が指定できます。

プラグインの遅延読み込み

プラグインの読み込み処理が遅いとシェルの起動時間も遅くなってしまうため、遅延読み込みをしておきたいです。遅延読み込みの設定は💡 Examples - sheldon docsDeferred loading of plugins in Zshに設定例が記載されていますので、これを設定していきます。

Sheldon本体に遅延読み込みの機能はありませんが、zsh-defersourceサブコマンドが出力するコマンドをカスタマイズできるテンプレート機能を組み合わせることで実現できます。

組み込みテンプレートとして、デフォルトの挙動であるsourceでプラグインを読み込むコマンドを出力するもの、PATHpathfpathにディレクトリを追加するコマンドを出力するものがあります(テンプレート名はそれぞれsourcePATHpathfpathです)。

自分でカスタムテンプレートを作成する場合は、templateテーブル配下に定義を追加します。

遅延読み込み用のテンプレートを追加する前に、pluginsテーブル配下にzsh-deferをプラグインとして追加しておきます。

plugins.toml
[plugins.zsh-defer]
github = "romkatv/zsh-defer"

次にtemplatesテーブル配下にdeferというキーで遅延読み込みのテンプレートを追加します。

plugins.toml
[templates]
defer = { value = 'zsh-defer source "{{ file }}"', each = true }

そして、遅延読み込みをしたいプラグインの定義に、このdeferテンプレートを値に持つapplyキーを追加します。

plugins.toml
[plugins.zsh-autosuggestions]
github = 'zsh-users/zsh-autosuggestions'
apply = ["defer"]

すると、sourceサブコマンドの出力にzsh-deferが付与されるようになり、対象のプラグインはzsh-deferを使った遅延読み込みが行われるようになります。

sheldon source
zsh-defer source "/Users/john/.sheldon/repos/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh"

読み込むファイルの指定

プラグインのリポジトリ内のどのファイルが読み込まれるかはmatchキーで定義されているということを上述しました。ただ、特定のプラグインで読み込むファイルを変更したい場合、グローバルオプションであるmatchキーの値を変更することは適切ではありません。

プラグイン単位で読み込むファイルのパターンを指定するにはuseキーを使います。

例えばzsh-autosuggestionsの場合、デフォルトのmatchキーによるファイル検出ではzsh-autosuggestions.plugin.zshが先にマッチします。しかし、zsh-autosuggestions.plugin.zshzsh-autosuggestions.zshsourceしているだけなので、useキーを使ってzsh-autosuggestions.zshを読み込むように設定できます。

plugins.toml
[plugins.zsh-autosuggestions]
github = 'zsh-users/zsh-autosuggestions'
use = ['{{ name }}.zsh']
apply = ["defer"]

これにより、sourceサブコマンドの出力は次のようになります。

sheldon source
zsh-defer source "/Users/john/.sheldon/repos/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.zsh"

Zshの補完定義プラグインの設定

Zshの補完定義がプラグインとして提供されている場合、プラグイン側の*.plugin.zsh内でfpathに追加する処理が書かれているため、通常はそのまま追加すれば問題ありません。

Sheldon組み込みのfpathテンプレートを使うと、プラグインとして提供されていないリポジトリやGist上のファイルを追加することができます。

ここでは例としてnnao45/zsh-kubectl-completion_kubectlを追加する方法を示します。

plugins.toml
[plugins.kubectl-completion]
remote = "https://raw.githubusercontent.com/nnao45/zsh-kubectl-completion/master/_kubectl"
apply = ["fpath"]

githubキーの代わりにremoteキーを使ってリポジトリ上の_kubectlのURLを指定します。そしてapplyキーを追加してfpathテンプレートを指定します。

このように設定するとsourceサブコマンドは次のようにfpathへの追加コマンドを出力します。

sheldon source
fpath=( "/Users/john/.sheldon/downloads/raw.githubusercontent.com/nnao45/zsh-kubectl-completion/master" $fpath )

プラグインの更新

プラグインの更新はlockサブコマンドに--updateオプションを付けて実行します。

sheldon lock --update

--updateオプションの代わりに--reinstallオプションを付けて実行すると強制的にプラグインが再インストールされます。

sheldon lock --reinstall

オプションなしのlockサブコマンドはロックファイルを再生成し、plugins.tomlで指定された通りにプラグインがインストールされているかチェックし、指定された通りになっていなければインストール・削除を行います。

sheldon lock

ロックファイルが存在しない場合はsourceサブコマンドの実行でもlockサブコマンドと同等の動作をします。

実行速度

実行速度についてはSheldonの作者による各種Zshプラグインマネージャーのベンチマーク結果を比較するリポジトリが参考になります。

https://github.com/rossmacarthur/zsh-plugin-manager-benchmark

ここでは26個のプラグインを追加して遅延読み込みなしでベンチマークが測定されています。

プラグインの読み込みのベンチマーク結果
Load timeより引用

この結果によれば、Sheldonのロード時間は高速な部類に入ります。

実行速度は追加するプラグインに依存しますし、遅延読み込みを利用できるものであればシェル起動時には影響を受けなくなりますので、ベンチマークを鵜呑みにせずに自身の環境で測定しながら試行錯誤することが大事です。

参考として、hyperfineを使って簡易的に実行時間を測定してみました。

私の場合、この記事の執筆時点では5つのプラグインを追加した状態でsheldon sourceの実行時間は7msぐらいです。

❯ hyperfine --shell=none 'sheldon source'
Benchmark 1: sheldon source
  Time (mean ± σ):       7.5 ms ±   1.1 ms    [User: 4.2 ms, System: 1.7 ms]
  Range (min … max):     6.3 ms …  13.9 ms    215 runs

ほとんどのプラグインを遅延読み込みしており、sheldon sourceの結果をZshが読み込む部分まで含めると10msぐらいになっています。

❯ hyperfine --shell "zsh --no-rcs" 'source <(sheldon source)'
Benchmark 1: source <(sheldon source)
  Time (mean ± σ):       9.6 ms ±   1.7 ms    [User: 6.9 ms, System: 2.6 ms]
  Range (min … max):     6.9 ms …  16.8 ms    186 runs

私の環境だとその他の設定も含めてZshの起動時間は140msぐらいです。何もロードしない素のZshだと起動時間は5msぐらいです。

❯ hyperfine --shell=none 'zsh -i -c exit' 'zsh --no-rcs -c exit'
Benchmark 1: zsh -i -c exit
  Time (mean ± σ):     137.4 ms ±  24.5 ms    [User: 48.7 ms, System: 58.0 ms]
  Range (min … max):   119.2 ms … 214.9 ms    13 runs

Benchmark 2: zsh --no-rcs -c exit
  Time (mean ± σ):       4.8 ms ±   1.3 ms    [User: 1.1 ms, System: 1.1 ms]
  Range (min … max):     3.7 ms …  14.9 ms    392 runs

頻繁にtmuxでセッションを生成していますが、体感的にも気にならないくらい速いです。

おわりに

Sheldonは従来のZshプラグインマネージャーに比べ、設定項目自体が直感的でわかりやすく、設定ファイルがTOML形式になっていることもあり可読性も高いです。それでいて実行速度も高速です。

他にない特徴としてはテンプレート機能によってスクリプトの読み込み処理を柔軟に拡張できることです。これによってプラグイン化されていないものも簡単に呼び出すことができます。

かなり使い勝手がよいと思いますので、ぜひ試してみてください。

脚注
  1. Intel環境でApple Silicon版をクロスコンパイルするとlibsslへのリンクに失敗する問題があり、現時点でIntel環境しか存在しないGitHub ActionsでCIを実行しているため、公式のビルド済みバイナリはIntel版しか提供されていません。一方、HomebrewではApple Silicon上でビルドしたBottleが提供されているため、Apple Silicon版のビルド済みバイナリをインストールすることができます。 ↩︎

Discussion

ログインするとコメントできます