Zshでプロンプトのホスト名をホスト名によって異なり、かつ読みやすい色にした。

はじめに
=======
この記事は、[Rakuten Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rakuten)の17日目の記事です。

楽天のR&D部門である[楽天技術研究所](http://rit.rakuten.co.jp/)でヒューマンコンピューターインタラクション関連の研究をしているmecabです。あだ名に英語のようなそれっぽいスペルを充てたら偶然かぶってしまっただけで、[形態素解析ツール](http://taku910.github.io/mecab/)とは関係ありません。

年末の大掃除として、ターミナルとシェルの環境を整えました。以下の通り、そこそこ実用的かつ、カラフルで楽しい感じになりました。

<img width="826" alt="Screen Shot 2016-12-13 at 2.55.10 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/51382/db0b27f2-c3b4-9804-d3cb-a7adf4ae1834.png">

「とりあえずemoji入れとけば良いと思うなよ💢💢💢」と怒られそうですが、プロンプトのホスト名の色つけだけはちょっと真面目に設定したので、これについて書きます。


ホスト名に個別の色をつけて識別しやすくする
===================================
意図しなかったマシン上でコマンドを実行してしまう事故を防ぐために、プロンプトにホスト名を表示したり、ホストによってプロンプトの色を替えたりすることは有効でしょう。

ホストごとにプロンプトの色を変えるにあたっては、愚直に行おうとすると、ホストごとに手動で色を割り振って、各ホストの設定ファイル（例えば`.zshrc`）に設定するという方法がありますが、マシンが増えるにつれて何色を割り当てるかを考えるのは億劫ですし、設定ファイルも共通化できないため、管理が面倒です。

この問題を解決するために、ホスト名から色を機械的に生成する方法があります。例えば、ホスト名のハッシュ値を計算して16進表記すると、先頭2文字は`00〜ff`の範囲にあるため、直接256色にマッピングすることができます[^1]。

ただ、実際に試してみたのですが、ホスト名によってはターミナルの背景色に近い色（例: 黒地に紺）が選択され、視認性が著しく悪くなってしまうことがありました。そこで、ホストに固有で、かつ、見やすい色を自動生成できるように工夫してみます。

なお、本記事は、256色のパレットが利用できるのターミナル環境を前提とします。

ホスト固有で視認性の高い色を生成する
===============================

とはいえ、シェルからターミナルの背景色を知ることはできません[^2]。そこで、背景色をホスト名に応じて設定した上で、背景色に応じた文字色として、白または黒のいずれか見やすい色を設定するという戦略をとることにします。

ここで、256色ターミナル環境において、カラーコードの値が実際にどのような色を表しているか理解する必要があります。カラーコードについて、askubuntu上に[参考になる投稿](http://askubuntu.com/questions/821157/print-a-256-color-test-pattern-in-the-terminal/821163#821163
)を見つけました。リンク先を見れば、`0`〜`15`はシステムカラー領域、`16`〜`231`は有彩色領域、`232`〜`255`が無彩色領域になっており、また有彩色領域については各面が6x6x6の立方体の色空間（カラーキューブ）として表現できることも分かります。さらに嬉しいことに、各平面について、上半分は白を、下半分については黒を、文字色として使えば見やすいようです[^3]。

以上をふまえて、有彩色領域のみから背景色を選択し、背景色に応じた見やすい文字色を実際に生成してみることにします。システムカラー領域はターミナルで実際に何色があてられているか分からないことから除外し、無彩色領域は簡単のために今回は除外しました。

では、実際に処理を行ってみましょう。


背景色の選択
-----------

まず、`md5sum`コマンドを使ってホスト名のハッシュ値を取得し、先頭2文字を`cut`コマンドで切り出します。Macの場合は`md5`コマンドに読み替えてください。

```zsh
bgval_hex=$(hostname|md5sum|cut -c1-2)
```

bgval_hexの範囲は`0x00`〜`0xff`の256通りありますが、実際に使う有彩色領域は`16`〜`231`の216通りしかありません。そこで、単純に割合を使って範囲を圧縮します（`$bgval_clipped`）。また、割った値に16のバイアスを与えて、実際に背景色として利用する数値を得ます（`$bgval`）。`$bgval_clipped`を変数として残したのは、後で述べる文字色の計算を簡潔に記述するためです。

`printf`を使うと`md5sum`から得られた16進数の文字列から直接計算できるので便利ですね。

```zsh
# Restict range: 0-255 => 0-215.
bgval_clipped=$(printf "%d" "215*0x${bgval_hex}/255.0")

# Bias range: 0-216 => 16-231
bgval=$(printf "%d" "${bgval_clipped}+16")
```


文字色の選択
----------

先に述べたように、カラーキューブの各面について、上半分は白、下半分は黒を文字色に使えば見やすいことが分かります[投稿](http://askubuntu.com/questions/821157/print-a-256-color-test-pattern-in-the-terminal/821163#821163)を再度見ると、有彩色領域について、カラーコードを18個（3行6列）ずつグループ化すれば、白、黒、白、黒と交互に、グループに対する適切な文字色が設定されることが分かります。つまり、`$bgval_clipped` を18で割った整数部が偶数なら白、奇数なら黒にすれば良いです。

```zsh
is_fg_black=$(printf "%d" "(${bgval_clipped}/18)%2")
if [ "${is_fg_black}" -eq 1 ]; then
  fgval=232 # system color INDEPENDENT white.
else
  fgval=255 # system color INDEPENDENT black.
fi
```

ここで、白と黒にはシステムカラーの白と黒ではなく、無彩色領域の白と黒を使いました。ターミナルの設定によらず、白と黒になるはずです。

エスケープシーケンスの出力
----------------------

256色のパレットを利用したターミナルの文字/背景色の変更は、以下のようにエスケープシーケンスを出力することで行なえます[^4]。

 - 前景色: `echo -e "\e[38;5;[カラーコード]m"`
 - 背景色: `echo -e "\e[48;5;[カラーコード]m"`

よって、上記を続けて出力し、続いてホスト名を出力することで、ホスト名毎の色付きでホスト名を出力できます。

```zsh
echo -e "\e[38;5;${fgval}m\e[48;5;${bgval}m"`hostname`
```

おわりに
=======
この記事では、ホスト名ごとに固有な色で、かつ見やすい色を機械的に生成する方法について説明しました。当然ながらこの方法はホスト名だけなく、任意の文字列に対して適用できます。ユーザ名に使用するのも良いかもしれません。

`hashed_color()`というシェル関数としてまとめたコードと、プロンプトとして利用したサンプルを https://gist.github.com/mecab/f1f8a87e06970ea9f46c8948897a9f31 に公開しました。 こちらでは`md5sum`ではなく、`md5`が存在する環境（MacとかBSD系）でも対応できるようにしています。また実際にプロンプトに導入するサンプルもコメントとして貼っているのでこちらも参照ください。

ちなみにぼくはターミナルに[iTerm2](https://www.iterm2.com/)を使っているのですが、iTerm2では色の設定[^5]のところにMinimum contrastという項目があり、これをつかうと背景色と文字色が近い色になったときに文字色を補正して見やすくしてくれます。少し入れておくと何かと便利かもしれません。

<img width="309" alt="Screen Shot 2016-12-13 at 2.47.53 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/51382/6b9d1dde-a344-1d9d-92ae-d1d8b873e3b2.png">


[^1]: http://absolute-area.com/post/6664864690/zsh%E3%81%A7%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E5%85%88%E3%81%AB%E3%82%88%E3%81%A3%E3%81%A6%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%82%8B%E3%83%9B%E3%82%B9%E3%83%88%E5%90%8D%E3%81%AE%E8%89%B2%E3%82%92%E8%87%AA%E5%8B%95%E3%81%A7%E5%A4%89%E3%81%88%E3%82%8B
[^2]: 特定の環境では[できるかもしれません](http://stackoverflow.com/questions/2507337/is-there-a-way-to-determine-a-terminals-background-color)。
[^3]: これはヒトの目が緑色に対して高い感度を持つことを利用しています。各平面の縦軸が緑色に対応しているからです。[投稿者がgistに上げているスクリプト](https://gist.github.com/HaleTom/89ffe32783f89f403bba96bd7bcd1263)も参照してください。
[^4]: http://d.hatena.ne.jp/kakurasan/20080701/p1
[^5]: Preferences → Profiles → Colors → Minimum Contrast