progrhyme's tech blog

主にIT関連の技術メモ

シェルスクリプトのパッケージ管理ツール「shelp」をGo言語で作った

これです。

https://github.com/progrhyme/shelp/

前回の記事でもちらっと触れましたが、あれから2週間近く経って、自分が欲しい機能はもう一通り揃ったところです。

何ができるツールなの?

だいたいbasherと同じようなことができます。 basherの機能概要については以前にQiitaに書きましたが、多少の相違点もあるので、改めて書き下します。

shelpの主な機能:

  • 任意のGitリポジトリをgit cloneして $SHELP_ROOT 下に配置し、「パッケージ」として扱います
    • リポジトリが bin/ とかルートディレクトリ直下に実行ファイルを含んでいればPATHを通します(カスタマイズ可能)
    • 取得するbranch, tag, commit hashの指定が可能です
  • include というシェル関数で任意のパッケージの任意のシェルスクリプトを読み込むことができます
  • YAMLの設定ファイルに取得したいパッケージリストを書いてまとめてインストールしたり、リストにないパッケージをまとめて削除できます
  • Bash, Zsh, fish shellをサポート。他のPOSIXコンパチなシェルでもたぶん動く

basherとの差異:

  • shelpにあってbasherにない:
    • 設定ファイルでパッケージリストを管理する
    • パッケージを任意の名前でインストール
    • インストール時に任意のgit urlに対応
    • インストール時にコミットハッシュを指定可能
    • パッケージをまとめて最新化できる
    • Bash, Zsh以外のPOSIX互換シェルの対応
    • 対象リポジトリに何も追加しなくていい(※basherだとパッケージインストール時の挙動をカスタマイズするのにpackage.shが必要)
  • basherにあってshelpにない:
    • シェルのcompletion(コマンド補完)機能
    • インストール時にマニュアルがあればsymlink作成
    • インストール時に依存パッケージがあれば追加インストール(再帰的にできるっぽい)

利用イメージ

シェルスクリプトなツールを試したい

GitHubに上がっているツールであれば、下のコマンドで取得できます:

shelp install <account>/<repository>

source して使うタイプのシェルスクリプトなら、 include <repository> path/to/script とやれば使えます。
リポジトリの直下か、 bin/ 以下に実行ファイルが置かれていたなら、もうPATHが通っているはずです。

要らなくなったら、次のコマンドでパッケージを削除できます:

shelp remove <repository>

もちろん、実行ファイルへのsymlinkがあれば一緒に削除してくれます。

自分で作った実行ファイルにさっとPATHを通す

そんなときにも便利です。

$EDITOR new-tool.sh # エディタでファイルを新規作成
chmod +x new-tool.sh
shelp link .

などとやると、カレントディレクトリを仮初めのパッケージと見なして、 $SHELP_ROOT/packages/ にsymlinkを作って、new-tool.shにPATHを通してくれます。
shelp link の対象ディレクトリはGitリポジトリでなくても大丈夫です。

自分が使うシェルスクリプトツールを管理したい

がっつり管理したい人向けのガイドです。
ここでは、私の利用例を紹介します。

今の私の環境の ~/.shelp/config.yml はこんな風になっています:

packages:
- from: progrhyme/git-wraps
- from: progrhyme/bash-git-push-carefully
- from: progrhyme/toolbox
- from: progrhyme/gcloud-prompt
- from: progrhyme/sh-pathctl
- from: b4b4r07/enhancd

このファイルをdotfilesリポジトリに入れて、 ~/.shelp/config.yml にsymlinkするようにしたので、環境が変わっても shelp bundle ですぐに必要なパッケージ一式をインストールできます。

上のリストの内、 gcloud-prompt, sh-pathctl, enhancd については、シェル起動時に ~/.bashrc~/.zshrc の中でshelpの include 関数によってパッケージ内のシェルスクリプトを読み込むようにしています。*1

あと、シェル起動時は1日1回 shelp bundleshelp upgrade (パッケージをまとめて更新)を実行するようにしました。

インストール方法

3つ方法がありますので、お好みでどうぞ。

  1. Homebrew (Linuxbrew) でインストール
  2. GitHub Releasesからバイナリ取得
  3. go get github.com/progrhyme/shelp

brewの場合のコマンドは下の通り:

brew tap progrhyme/taps
brew install shelp

シェルでshelpを有効にするには、以下を ~/.bashrc, ~/.zshrc, ~/.config/fish/config.fish 等に追記して下さい:

# fish以外
eval "$(shelp init -)"

# fishの場合
shelp init - | source

このコマンドでやっていることは下の3つです:

  • $SHELP_ROOT 環境変数の設定
  • $SHELP_ROOT/bin$PATH に追加
  • include 関数の定義

ドキュメント

Material for MkDocsを使って、Netlifyで公開してみました。

https://go-shelp.netlify.app/

拙い英語ですが、細かいユースケースや設定ファイルの書式も載せていますので、shelpを使う場合は役立つと思います。

shelp自体の紹介は以上です。

以下では、開発に至った経緯を述べます。
やや内省的な、ともすればポエムっぽい内容になりますので、興味のない方は読み飛ばしてください。

開発の経緯

どうしてまたシェルスクリプトのパッケージ管理ツールを、しかもGo言語で作ったのか、というところを述べます。

前回の記事でも触れたように、basherを使い出す前はclenvという自作ツールを使っていました。
こちらはbasherと同じくシェルスクリプト製で、実行ファイルはBashで書いています。

前回の記事に書いたように、basherに出会う前からclenvのイケてないところは色々と気になっていました。
作り直すことも考えていたのですが、その際には別にシェルスクリプトで書く必要はないよなぁと思っていました。
むしろ、シェルスクリプトで書くことに限界を感じていたので、これ以上はシェルスクリプトで書くべきでない、とまで思っていました。

シェルスクリプトの管理ツールをシェルスクリプトで書く必要はない

basher然りclenv然りですが、色んなシェルに対応しているのに、ほとんどBashで書かれています。
rbenvやanyenvもそうですね。

各々のシェル環境で解釈される rbenv init - のようなコマンドの出力結果は各種シェルに対応させる必要がありますが、コマンド実行するものであれば、POSIX準拠じゃなくてもよいわけですね。(もちろんその環境で動くものでないといけませんが)
…であれば、別にシェルスクリプトである必要さえないわけです。

シェルスクリプトによる開発でつらいところ

POSIXシェルよりBashは幾分楽だとはいえ、そもそも高級プログラミング言語に比べて機能が貧弱なので、ぶっちゃけ最初からつらいです。

特に自分がつらみや限界を感じていた点は以下です:

たぶん、自分のためだけの OR 特定の環境だけで動けばいいツールを書くなら、もっとずっと楽だと思います。

シェルスクリプト以外で書かれたツールの例

けっこう色々あります。

シェルスクリプトのパッケージ管理ツールっぽいものだと、最近Rust製のrossmacarthur/sheldonというのを見つけました。
READMEをざっと見たところ、Zshプラグインマネージャーであるzplugに機能がよく似ていることに気づきました。

他の例としては、 https://dotfiles.github.io/ で紹介されているdotfilesの管理ツールが挙げられます。
シェルスクリプト製のものもありますが、PythonやGo, Rustで書かれたものもあります。

直近で見つけたものだと、 https://starship.rs/ というRust製のプロンプト設定ツールもありますね。

basherへの不満(?)

特に実用上で不便はなかったのですが、強いて挙げれば shelp bundle みたいな機能がないことと、macOSbasher link が動かなかったことぐらいでしょうか。

まあ、できそうだから作ってみようか、ぐらいのノリもありました。

そしてGoに至る

プログラム言語を選ぶにあたっては、ランタイムのことを考えたくないので、クロスプラットフォームでバイナリを作れるものがいいだろうなと思いました。

Rustも考えたのですが、それなりに思い通りにコードが書けるようになるまでけっこう長く修行しないといけなさそうだったので、諦めてGoにスイッチしました。

Goも2〜3年ブランクがあり、簡単なCLIを作ったぐらいの経験しかありませんでしたが、言語仕様が小さいのでRustよりはすぐにコードが書けるようになりそうな見込みがありました。

遡ってみたところ、このツイートの数時間後に、shelpに1st commitしていました。

Go言語で開発したことによって、上に挙げたようなシェルスクリプトでつらかったところは全て解消されました。

Goの標準ライブラリを使っていれば、プラットフォーム間のコマンド実装の差異など気にしなくて済みます。
また、サードパーティー製も含めれば無数といっていいほど多くの外部ライブラリを利用できるので、コマンドライン引数の解釈やYAML対応などの機能も比較的簡単に実装していくことができました。

その内、Rustもまた再入門したい気もしますが、当面はshelpのメンテや追加開発を含めてGoを使っていくことになる気がします。

まとめ

以上、シェルスクリプトのパッケージ管理ツール「shelp」の紹介と、Go言語で開発した経緯について述べました。

よろしければお試しください。

気に入ったらスターを押してもらえると、励みになります。

脚注

*1:1つのURLで上手く抜き出せませんでしたが、設定例としては次の2コミットが参考になるかもしれません: progrhyme/dotfiles@f4a6b30, progrhyme/dotfiles@881af91