自作コマンド配布ツールとしてDockerを使う

# コマンド配布ツールとしてDockerは使えるか？

まずは論よりなんとやら。Docker導入済みの方は、下記のコマンドを実行してみてください。

```bash:Terminal
$ docker run -i --rm greymd/cureutils cure precures
```

すると、下記のように[Dockerhub](https://hub.docker.com/)からDockerのイメージの取得が始まり、そのイメージに含まれる`cure`コマンドが実行されます。このコマンドは、[以前私が自作したコマンド](http://qiita.com/greymd/items/666a26459aa1bd4cb06c)で、プリキュア[^about_precure]全員[^definition_of_alprecure]の名前を表示できます。

[^about_precure]: 日本が世界に誇る国民的アニメ。2017年1月現在「魔法つかいプリキュア」絶賛放映中！

[^definition_of_alprecure]: 2017年1月時点で、TVシリーズのメインヒロイン44人。厳密には「伝説の戦士プリキュア」ではない「キュア」のつかない戦士に変身する女の子も含みます。残念ながら満と薫、レジーナなどは含まないので悪しからず。

```bash:Terminal

Unable to find image 'greymd/cureutils:latest' locally # Dockerhubからイメージの取得が始まる
latest: Pulling from greymd/cureutils

efd26ecc9548: Pull complete
a3ed95caeb02: Pull complete
d1784d73276e: Pull complete
72e581645fc3: Pull complete
9709ddcc4d24: Pull complete
7013ecfd652c: Pull complete
66373b9c2e12: Pull complete
2a6578ab609c: Pull complete
4356bd31ea9f: Pull complete
34ea4e1d4a33: Pull complete
Digest: sha256:8ae33b900e562e9cbafb8bd6dda45b658521b9e81d999bd5bfd34e40c5a61015
Status: Downloaded newer image for greymd/cureutils:latest
キュアブラック # コマンドが実行される
キュアホワイト
シャイニールミナス
キュアブルーム
キュアイーグレット
キュアドリーム
キュアルージュ
キュアレモネード
# (中略)
```

```bash:Terminal
# 一旦イメージを取得してしまえば、以降はスムーズにできる
$ docker run -i --rm greymd/cureutils cure precures
キュアブラック
キュアホワイト
シャイニールミナス
キュアブルーム
# (中略)
```

この`cure`コマンドを使うためには、本来はRuby2.0.0以上を用意して`gem`でインストールする必要があります。しかし上記の方法で、それらが入っていない環境でもDockerを使ってコマンドを実行することができました。

とはいえ、わざわざ`cure`コマンドを使うのに、毎回`docker...ほにゃらら`などと打っていられないと思います。そういう時は、下記のような関数を`.bashrc`に追加します。

```bash:.bashrcの一部
cure () {
    [ -t 0 ] && T="t" || T=""
    docker run -i$T --rm greymd/cureutils cure "$@"
}
```

すると、まるでホスト上に`cure`コマンドがインストールされたかのように、`cure`コマンドを使うことができます。標準入力をフィルターできますし、オプションやコマンド引数も動作します。実際にホストに直接`cure`コマンドはインストールしていないにもかかわらず、です。

```bash:Terminal
$ cure echo -a -p cure_heart -q
プリキュアラブリンク！
L! O! V! E!
みなぎる愛！ キュアハート！
響け愛の鼓動！ドキドキプリキュア！
愛を無くした悲しいジコチューさん、
このキュアハートがあなたのドキドキ取り戻してみせる！
あなたに届け！マイスイートハート！
```

```bash:Terminal
$ echo キュア{ハート,ダイヤ,エース,スペード} | xargs -n 1 | cure grep
キュアハート
キュアエース
```

このような方法で、ホスト側からの標準入力を、コンテナ内のコマンドでフィルタリングして、ホスト側の標準出力に結果として出力できます。この方法を知った時「自作コマンド配布のためにDockerのイメージ使うのってどうなんだろう？流行る可能性はあるのだろうか？」と思ったので、以下、作り方をざっくりとまとめ、軽く考察します。

# 試した環境

Mac OSX Yosemite 10.10.5の端末上で実施。
zshを使って動作確認してますが、多分bashでも動きます。

```bash:Terminal
$ zsh --version
zsh 5.0.6 (x86_64-apple-darwin13.3.0)
```

DockerはMac OSXのネイティブ版Dockerを使います。

```bash:Terminal
$ docker --version
Docker version 1.12.5, build 7392c3b
```

# 作り方

## 動かしたいコマンドが動くDockerのイメージを作る

例えば私の場合、`cure`コマンドを動かすために下記のようなDockerfileを作りました。依存するDockerイメージとして`ruby:2.3.0`を指定してあります(OS自体はDebianです)。やっていることは、リポジトリからソースをクローンして、rakeでビルドし、bundleでインストールしています。この一連の作業で、`cure`コマンドが`PATH`が通っている`/usr/local/bundle/bin/cure`に置かれました。
https://github.com/greymd/dockerfiles/blob/master/cureutils/debian/Dockerfile

また、今回の場合`cure`コマンドは日本語を出力します。なので、イメージの文字エンコーディングの諸々の設定を全て`ja_JP.UTF-8`にしてあります。

```Dockerfile:Dockerfile
FROM ruby:2.3.0
RUN apt update && \
    apt install -y locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.utf8 && \
    cd ~ && \
    git clone https://github.com/greymd/cureutils.git && \
    cd cureutils && \
    gem install bundler rake && \
    bundle install && \
    rake build && \
    find ./pkg/ -type f | head -n 1 | xargs gem install && \
    cd .. && \
    rm -rf cureutils
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
```

`Dockerfile`があるディレクトリでビルドします。

```bash:Terminal
# docker build -t <Dockerhubのユーザ名>/<イメージ名> .
$ docker build -t greymd/cureutils . 
```

## DockerhubにPushする

Dockerのイメージを登録できるサービスであるDockerhubにイメージを登録します。事前にDockerhubのアカウントを作成するなどの作業が必要です。こちらの記事によくまとまっていたので、説明は割愛します。
http://qiita.com/moru3/items/32931813db81d891effb

```bash:Terminal
$ docker login
$ docker push greymd/cureutils
```

こうして、`docker pull`でイメージが取得できれば成功です。

```bash:Terminal
$ docker pull greymd/cureutils
```

## `.bashrc`に追加

再掲になります。

```bash:.bashrc
cure () {
    [ -t 0 ] && T="t" || T=""
    # docker run -i$T --rm <Dockerのイメージ名> <コマンド名> "$@"
    docker run -i$T --rm greymd/cureutils cure "$@"
}
```

`[ -t 0 ] && T="t" || T=""`という箇所で、標準入力が無ければ `T`という変数に"t"という文字列を入れ、標準入力があれば空文字を入れます。これで`docker run`コマンドの`-t`オプションの有無を制御しています。

`-t`オプションがあると、標準入力がコンテナ内のコマンドまで渡らないので、フィルタのような動きができません。そこで標準入力がある場合は`-t`を無効にしています。逆に、常に`-t`オプションを無効にすると、スリープをかけながら標準出力するコマンド[^cureecho]が正しく動かないため、基本的には有効にしています。

[^cureecho]: `cure echo`コマンドのことです。

## 配布する時
人様に自作コマンドを使ってもらいたいときは、
1. Docker入れて！
2. `.bashrc`に `my_command () ...` って関数入れて！

ってやればできる。(2は[zplug](https://github.com/zplug/zplug)のほうが良いかもしれない。）

# 考察

## この方法の利点
* 環境の影響を気にせずコマンドを配布できる。
    * コマンドの動作が標準入出力のみで決定するようであれば、環境の差分はないハズ？
* 導入が面倒なコマンドを配布するのに向いている
    * パッケージ管理システムで配布されておらず、色々入れてビルドが必要なものとか。
* 異なるバージョンのインタプリタが必要なコマンドを共存するのに役立つ。
  * 例: コマンドAはRuby2.0でしか動かない。コマンドBはRuby2.1でしか動かない。じゃあ片方Dockerにして、ホストの`PATH`の`ruby`はRuby2.1にしよう、みたいな。

## この方法の欠点

* 標準入出力以外の要素が介在すると、コマンドが正しく動くとは限らない。（例: ディレクトリやファイル、GUI、ネットワークなど）
* ホストとコンテナ間の情報のやり取りのオーバーヘッドがあるのか、比較的動作が遅い。コマンドに巨大なデータを投げるのには向かない。（例: `grep`, `awk`など）

# 所感
標準入出力以外のホストのリソースを簡単に使えないため、総合的には微妙な気がしました。ただ、コマンドをこんな感じでDockerのイメージと抱き合わせで配布しやすくするための、パッケージ管理ツール的なものがあれば、需要はありそうだと思いました。

# おまけ（活用例）

Mac OSXだと`grep`コマンドがデフォルトではBSD版ですが、この手法を使うことでGNU版のコマンドが使えます（coreutils使えって話ですが）。

```bash:Terminal
# BSD版にはない-Pオプションが動く
$ seq 10 20 | docker run -i ubuntu:16.04 grep -P '.0'
10
20
```