シェルスクリプトのパッケージマネージャー「basher」の機能概要と使い方

# はじめに

シェル環境で作業することの多いみなさんは、シェルスクリプトのパッケージ管理ツールが欲しいと思ったことはあるでしょうか？
私はあります。一時期その欲求が高まって、つい自作してしまったほどです。[^1]

[^1]: 4年ぐらい前に作って、ずっと自分でドッグフーディングしていました。（参考）[&quot;clenv&quot; というシェルスクリプトのパッケージ管理ツールのようなものを作った - weblog of key_amb](https://keyamb.hatenablog.com/entry/clenv-release-v0.1)。最近、basherに乗り換えつつあります。

本稿では、最近見つけたシェルスクリプトのパッケージ管理ツールを1つ紹介します。
こちらです:

- https://github.com/basherpm/basher

「basher」という名前で、一見Bash専用に見えますが、実はZshとfishにも対応しているそうです。

> Even though it is called basher, it also works with zsh and fish.

私自身、BashとZshで動作を確認しており、これから常用していく予定です。

中身は完全にシェルスクリプトで実装されています。

# 機能概要

READMEやコマンドのヘルプをざっと見たところ、以下のような機能があります。

- GitHub or 任意のサイトからパッケージを取得してインストール
  - リモートからの取得手段は `git clone` のみだと思います
  - sshスキームにも対応しているようです
- ファイルシステム内のパッケージをインストール
- インストールしたパッケージ内の実行可能ファイルにPATHを通す
- `include` 関数によって、インストールしたパッケージ内の任意のシェルスクリプトを読み込む（ `source` or `.` 相当）
- Bash / Zshの場合、パッケージの設定によって、completionファイルを適切に配置し、初期化処理の中で読み込む
- インストールしたパッケージの一覧、更新、削除

以下では、インストール手順や使い方の説明をします。

# インストール方法
## basherの取得

```sh
git clone https://github.com/basherpm/basher.git ~/.basher
```

## シェル環境に読み込む

2020-05-22現在は、READMEとは少しコマンドを変える必要があります。[^2]

[^2]: https://github.com/basherpm/basher/pull/77 で変更されたそうで、まだREADMEが追従してないようです。些細な修正ですが、[PRしてみました](https://github.com/basherpm/basher/pull/79)。

Bashの場合、以下をbashrcに加えます:

```sh:bashrc
export PATH="$HOME/.basher/bin:$PATH"
eval "$(basher init - bash)"
```

Zshの場合、以下をzshrcに加えます:

```sh:zshrc
export PATH="$HOME/.basher/bin:$PATH"
eval "$(basher init - zsh)"
```

# 使い方
## パッケージのインストール

GitHubにホストされているリポジトリであれば、 `basher install <account>/<repository>` の形式でインストールできます。

例:

```sh
basher install sstephenson/bats
```

これにより、batsリポジトリが `~/.basher/cellar/packages/` 配下にcloneされ、以下のように `bats` コマンドが `~/.basher/cellar/bin/bats` にsymlinkされます。

```
$ ls -l ~/.basher/cellar/bin/
lrwxrwxrwx 1 progrhyme progrhyme 62  5月 22 23:37 bats -> /home/progrhyme/.basher/cellar/packages/sstephenson/bats/bin/bats
```

これで `bats` コマンドがPATHに入った状態で使えるようになりました。

ソースも追ってみたところ、basherはデフォルトでパッケージの `bin/` ディレクトリか、 `bin/` ディレクトリが無ければルートディレクトリ直下から実行可能ファイルを探してsymlinkを作成してくれるようです。

## include関数でシェルスクリプトを読み込む

[機能概要](#機能概要)で説明したように、basherで実装されている `include` 関数を使うことで、インストールしたパッケージの任意のシェルスクリプトを読み込むことができます。
構文としては、 `include <package> <path>` のようになります。

先に例としてインストールしたbatsは、シェル環境に読み込んで使うタイプのツールではないので、ここでは手前味噌ですが、拙作のbash-loggerというユーティリティをインストールして使ってみます。

```sh
$ basher install progrhyme/bash-logger                
Cloning into '/home/progrhyme/.basher/cellar/packages/progrhyme/bash-logger'...
: # 略
```

インストールしたbash-loggerに含まれる `logger.bashrc` を `include` して、定義されているロガー関数を使ってみます。

```sh
$ include progrhyme/bash-logger logger.bashrc
$ log.warn "blah blah blah"
2020-05-23 00:12:11 [WARN] blah blah blah
```

このように、パッケージのシェルスクリプトを読み込んで、定義されている関数等を利用することができます。

## ファイルシステム内のパッケージをインストール

`basher link` コマンドを使うと、ローカルのシェルスクリプトをパッケージとして擬似的にインストールすることができます。
この機能は、特にシェルスクリプトによるツール開発をよく行う人に便利だと思います。
あるいは、リポジトリの構造がbasherに対応していないときに、特定のディレクトリをパッケージとしてインストールしたり、手元で[後述のpackage.sh](#パッケージにpackageshを配置する)を配置してインストールするようなユースケースが考えられます。

私の場合、いくつか自作のシェルスクリプト製のツールがあるので、動作確認時にこの機能を使いました。

```sh:コマンド例
basher link path/to/repos/shove progrhyme/shove
```

注意点として、パッケージ名として与える文字列（上では `progrhyme/shove` ）は `<account>/<repository>` の形式でないとエラーになります。（何度か気づかずに軽くハマりました。）

挙動としては、下のようにsymlinkされる形になります。
ので、実体を変更したら、インストールされたパッケージ側にも即座に反映されることになります。

```sh
$ ls -l ~/.basher/cellar/packages/progrhyme/
lrwxrwxrwx 1 progrhyme progrhyme 27  5月 23 00:29 shove -> /path/to/repos/shove
```

## パッケージにpackage.shを配置する

パッケージのルートディレクトリ直下に `package.sh` というファイルを配置しておくことで、該当パッケージをインストールする際の挙動をカスタマイズすることができます。

README.mdのままですが、下がサンプルとなります:

```sh:package.sh
BINS=folder/file1:folder/file2.sh
DEPS=user1/repo1:user2/repo2
BASH_COMPLETIONS=completions/package
ZSH_COMPLETIONS=completions/_package
```

それぞれの変数では、次の内容を指定することができます:

- `BINS` ... 実行可能ファイルとして配置するファイルのパッケージ内のパス
- `DEPS` ... 当該パッケージが依存するパッケージ。同時にインストールされる
- `BASH_COMPLETIONS` ... bash-completionとして配置されるファイルのパッケージ内のパス
- `ZSH_COMPLETIONS` ... zsh-completionとして配置されるファイルのパッケージ内のパス

いずれも `:` を区切り文字として複数指定することができます。

この機能を試したところ、次の制限事項があるようです:

- `DEPS` で指定したパッケージは、当該パッケージをアンインストールしても同時にアンインストールされることはない。

# 制限事項
## macOSでcoreutilsが必要

2020-05-24現在、 `basher link` が動作しませんでした。
[realpath(1)](http://man7.org/linux/man-pages/man1/realpath.1.html)または[GNU readlink](https://linuxjm.osdn.jp/html/GNU_coreutils/man1/readlink.1.html)がないと動かないようです。[^3]

[^3]: こちらも[修正PR](https://github.com/basherpm/basher/pull/80)を送っています。

いずれも `brew install coreutils` でインストールされます。
realpathがあればそちらが優先して使われるので、GNU readlinkを使うことはないでしょう。

# 終わりに

以上、basherの機能と使い方をざっと紹介しました。

basherは仕組みがわかりやすく、必要十分な機能が揃っているように思います。
basherを使ってシェルスクリプト製ツールを管理すると、gitのsubmoduleで管理するよりは楽で、野良でシェルスクリプトを入れるよりも見通しがよくて便利なのではないかと思います。
また、 `include` が便利なので、ひょっとしたらHomebrewなどでインストールしているツールも、シェルスクリプトのものはbasherに移行する価値があるかもしれません。

私が4年前にパッケージ管理ツールを自作したときは、basherの存在に気づかなかったのですが、実は6年前には下のブログ記事が公開されていました。
もし当時、気づいていれば、ツールを自作しようとは思わなかったかもしれません。

# 参考

- [A Package Manager For Shell Scripts - Juan Ibiapina](https://juanibiapina.github.io/articles/basher-a-package-manager-for-shell-scripts/) ... 作者によるbasherの紹介ブログ

# 脚注