Bats-coreにreadlinkf（readlink -fのシェル関数版）をコントリビュートしました

タイトルのとおり [Bats-core: Bash Automated Testing System (2018)](https://github.com/bats-core/bats-core) プロジェクトに `readlink -f` の シェル関数実装版である [readlinkf](https://github.com/ko1nksm/readlinkf) を[コントリビュート](https://github.com/bats-core/bats-core/pull/312)しました。`readlinkf` は以前に「[POSIX準拠でもreadlink -f （完全互換版）](https://qiita.com/ko1nksm/items/873cfb9c6ceb6ef32ec9)」で記事にしていますが、他のプロジェクトに組み込みやすくするためにコードをより一般向けに変更しています。

**補足** Bats-core は Bats の後継プロジェクトです。Bats はメンテナンスされなくなっており（作者の反応がない）事実上開発終了状態です。

## readlinkf ってなに？

`readlink -f` (`--canonicalize`) コマンド相当でネストされているシンボリックリンクを解決して絶対パスに変換するシェル関数です。普通に `readlink` コマンドを使えば？と思うかもしれませんが、実は `-f` オプションは macOS や FreeBSD で対応していません。そのため GNU 互換の `greadlink` コマンドが入っていればそちらに使用するという対応がしばしば見受けられます。しかしながら `greadlink` コマンドは必ずインストールされているわけではありませんし、場合によっては `readlink` コマンドですらインストールされていない場合があります。そのため `readlink` コマンドの代わりに `cd`と `ls` コマンドを使って（Bash 以外も含めた）POSIX シェル用に POSIX 準拠で実装してポータビリティをもたせています。

## なぜ作ったの？

`readlink -f` というのはある程度の規模のシェルスクリプトを作るときには必須の機能です。一般的にコードが大きくなると複数のファイルに分割してライブラリとして外部スクリプトを読み込むようにすると思います。外部スクリプトを読み込むには `.` コマンド（または `source` コマンド）を使用すればよいのですが、このコマンドはスクリプトファイルがシンボリックリンクの場合にシンボリックリンクがある場所からの相対パスで外部スクリプトを読み込みます。通常は外部スクリプト等を例えば `/usr/local/lib/my-prog/` 以下にインストールし、実行ファイルへのシンボリックリンクを `/usr/local/bin` 以下に作成するので、外部スクリプトを読み込むには最初に実行ファイルのシンボリックリンクを解決してインストール場所を知る必要があります。（なお外部スクリプトを読み込むのに必要な処理なので `readlinkf` 自体を外部スクリプトにすることができません。）

この問題は昔から何度も話題になっており、解決方法もいくつも([\[1\]][1], [\[2\]][2], [\[3\]][3])見つかるのですが、細かいバグがあったり、Issue が放置されていたり、テストがなかったり、パフォーマンスが悪かったり、コードが長かったり、Bash 依存していたり、ライセンスの問題があったりなど、いくつもの問題を抱えており、信頼できる実装を見つけることができませんでした。そこで開発したのが `readlinkf` です。

[1]: https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac
[2]: https://stackoverflow.com/questions/59895/how-to-get-the-source-directory-of-a-bash-script-from-within-the-script-itself
[3]: https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself


その名の通りどうしても必要な機能である `readlink -f` 相当のみに絞って実装しているため、必要最小限のコードで短く可能な限りパフォーマンスも悪くならないようにしています。Debian、macOS、FreeBSD、Cygwin 等の各種環境と各種 POSIX シェルでテストしており、実際にいろいろなパターンのシンボリックリンクを作成して GNU coreutils の `readlink` の実行結果と一致していることを確認しています。（テストの実行結果は[こちら](https://cirrus-ci.com/github/ko1nksm/readlinkf/master)）

## Bats-core にコントリビュートするまで

Bats-core のプロジェクトを検索すればわかると思いますが、特定の環境（特に macOS）で動作しない等の理由で関連のコードが何度か作り直されているのがわかると思います。私が気づいたときにはすでに対応が進んでおり、完璧でなくても動けばいいかーぐらいに思って見ているだけだったのですが、なかなか問題が収束せず私が信頼できないと切り捨てたコードが採用されそうな流れになりつつあったので、それが使われるぐらいならと[やる気](https://github.com/bats-core/bats-core/issues/307#issuecomment-627699310)を出してしまいました。

もともと `readlinkf` は~~自分の趣味で~~スクリプトに埋め込んでもじゃまにならないように~~コードゴルフで~~可能な限り短い行数で実装していたので、このままでは駄目だろうなと思いつつももしかしたら~~ワンチャンあるかも~~このままで良いと言われるかもと思ったので、意見を聞くためという名目で（対応するよーと一言書くだけで）プルリクを作成しました。そして当然のごとく読みやすいほうが良いと言われたので普通のスタイルに全面的に書き直しました。

最初のプルリク以降は、意見を聞きつつ対応していきました。（コードゴルフ以外で）悩んだのがどこまでコードを相手のプロジェクトに合わせて修正するかです。Bats-core は Bash で作られていますが、`readlinkf` は POSIX シェル用として作っているためコードの書き方が異なります。例えば Bats-core ではローカル変数に `local` キーワードを使用してますが、POSIX シェルでは使えません。また一部のシェルのためのワークアラウンドも存在します。通常であれば相手のプロジェクトのコーディング規約に合わせてコードを書くようにしているのですが、今回は私のプロジェクトが上流という扱いであるため、のちの上流の修正を反映することを考慮してなるべくコードを変更しないほうが良いという選択肢もあります。

そこでコミットを２つにわけ、１つ目のコミットは上流のコードそのままで、２つ目のコミットで Bats-core 専用に修正する方針に（私の中で）しました。数回のレビューを経て最終的には２つ目のコミットは作らなくてよく、上流のコードがほぼそのままマージされたので、Bats-core のコーディング規約を読んだよとチェックを入れつつも、その規約に従わないという、わがままなコードが含まれることになりました。（例えば変数へ代入する時はダブルクォートで括ってとレビューで言われましたが、某シェルのためのワークアラウンドも兼ねているという理由でお断りしてたりします。笑）もっとも `readlinkf` のライセンスは自由に変更が可能で著作権表示も不要な Creative Commons Zero v1.0 Universal にしているので将来いつの間にか修正されるかもしれませんが。

## さいごに

今回思いもかけず有名な Bats-core で採用されることになり、第三者のレビューも行われたのでより信頼性が上がり他のプロジェクトでも採用しやすくなったと思います。`readlink -f` の問題はいろんなプロジェクトで何度も繰り返し発生しています。もしどこかのプロジェクトで同じ問題で悩んでるのを見かけたら、この `readlinf` を使用してプルリクを作成してみてはいかがでしょうか？