新macOS Catalina が zsh を標準にするって聞いたので Mojave のまま移行してみた

# TL;DR

とりあえず以下の要領で bash の各種スクリプトから zsh にコピペしてちょっと直せばなんとかなります。

ただしコマンド履歴と補完だけは気をつけろ。

|bashで使ってたファイル|zshで使うファイル|備考|
|:--|:--|:--|
|~/.bash_profile|~/.zprofile|`source .bashrc` してた場合、それより**前**の記述をコピペする|
|~/.bashrc|~/.zshrc|bash のログインシェルは読まなかったが、 zsh はログインシェルでも読む点に注意|
|~/.bash_profile|~/.zlogin|`source .bashrc` してた場合にのみ作る。それより**後**の記述をコピペする|
|~/.bash_logout|~/.zlogout||

----

# 発端

macOS Catalina で標準のシェルが zsh になるって聞いたので、後で必要に迫られる前に慣れておこうと思いました。

# 想定読者

多少bashをカスタマイズしてる人。
bash において `source` と `.` が同義であることくらいはご存知の前提です。

# 目標

- bash使いだった筆者がzsh環境を整える。
    - 元の環境
        - macOS Mojave 標準の bash
    - 新しい環境
        - macOS Mojave 標準の zsh
- bashで行っていたコマンドライン補完をzsh環境にも整える。

# 環境

macOS Mojave on MacBook Air (mid 2013)

```bash
bash --version
```
> GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
> Copyright (C) 2007 Free Software Foundation, Inc.

```zsh
zsh --version
```
> zsh 5.3 (x86_64-apple-darwin18.0)

# 手順

## まず標準シェルの切り替え

まず標準を zsh に変えるため「macOS 標準 シェル 変更」などでググります。

方法はいくつかありますが GUI で変える方法が Apple から説明されていますね。[^2]

[^2]: 「zsh を Mac のデフォルトシェルとして使う - Apple サポート」https://support.apple.com/ja-jp/HT208050 (2019年11月28日閲覧)

初めて zsh を起動した時は何やら初期設定的な画面が出るらしい[^1]のですが…僕がやってみたら、なぜか普通にシェルが現れました。

[^1]: 「zshで初回起動したときに対話的にzshrcを作成する - Qiita」https://qiita.com/mollifier/items/6481de1aa2d1033ad2ec (2019年8月7日閲覧)

前にセットアップしたんだっけ…? と思い `ls ~/.z{sh{env,rc},profile,login}` してみたものの、設定ファイルは見つからず。

もしかしたら上記の方法で移行する際には出ない画面かもしれないので、このまま自力で bash から移行します。

## bash 起動スクリプトの確認

さて、 bash のカスタマイズに使うのはこの辺ですよね。

|パス|用途、実行タイミングなど|
|:--|:--|
|~/.bash_profile|ログインシェルの初めに読まれる。オレオレエイリアスの宣言とか、プロンプトのカスタマイズはここで行ってるはず|
|~/.bashrc|インタラクティブ（対話的）シェルの初めに読まれる。実はログインシェルでは読まないので、読んで欲しいという人は .bash_profile にインクルードしてるとか|
|~/.bash_logout|ログインシェルの終わりに読まれる。 .bash_profile でデーモンを立ち上げたりしたらここで後片づけを行う|

## 設定の移植

一方前述の初期設定に関する資料[^1]によると、以下のファイルが初期設定により作られるはずだったらしいです。

`man zsh` してみると "STARTUP/SHUTDOWN FILES" の節で説明されているので間違い無いでしょう。

|パス|bashでいうと?|用途、実行タイミングなど|
|:--|:--|:--|
|~/.zshenv|(zshオリジナル)|zsh 起動時に毎回読まれる。絶対走るマン|
|~/.zprofile|~/.bash_profile|ログインシェルの初めに読まれる|
|~/.zshrc|~/.bashrc|対話的シェルの初めに読まれる。オレオレエイリアスの宣言とか、プロンプトのカスタマイズなんかはここで行うのが良さげ|
|~/.zlogin|~/.bash_profile|ログインシェルの初めに読まれるが、そのタイミングは .zprofile や .zshrc より後|
|~/.zlogout|~/.bash_logout|ログインシェルの終わりに読まれる。終了方法が `exit` コマンドなのか `logout` コマンドなのか、はたまた EOF なのかによらない|

`~/.bash_profile` は zsh 移植時に**割れます。**遊戯■のメインフェイズのように、ほぼ同じ役割のスクリプト2つが別のスクリプトを挟んで走ります。

# 実際に移植する

さて情報が出揃ったところでパパッと移植しちゃいます。

## 共通

元の bash スクリプトに bash 特有と言われる文法を使ってるとハマる可能性があります。ご注意ください。

## 環境変数の移植

環境変数の中でも、特に開発ツールの場所を示すもの (例えば `$ANDROID_SDK_ROOT`) は ~/.zshenv に入れるのが良さそうです。
zsh の起動形態に関係なく読まれるので、確実に定義されます。

```zsh:~/.zshenv
# Android SDK の場所
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"
```

それ以外は ~/.zprofile の方に移動します。
(もし `source .bashrc` した後に定義してるものがあれば、それらは ~/.zlogin に移動してください。)

```zsh:~/.zprofile
# macOS は標準の /etc/zprofile で一度 PATH を改変するため、その直後に走るこっちで PATH 通す
export PATH="node_modules/.bin:${ANDROID_SDK_ROOT}/tools:${ANDROID_SDK_ROOT}/platform-tools${PATH:+:$PATH}"
```

## 補完機能をつける

補完機能はインタラクティブシェルに関わるものなので、 ~/.zshrc に移動します。

なおコマンドライン補完を有効化する方法は **bash とだいぶ異なる**ので、各補完スクリプトのマニュアルを読んで zsh 用のセットアップ手順を踏んでください。

```zsh:~/.zshrc
# 補完機能の有効化と初期化
autoload -U compinit
compinit

# pip 自動補完
eval "$(python -m pip completion --zsh)"

# git in Xcode command-line tools 自動補完
# 現物見ると複製して使ってねって書いてありますが、僕は直接参照してます
zstyle ':completion:*:*:git:*' script /Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-completion.zsh
```

## プロンプトのカスタマイズ

プロンプト改造もインタラクティブシェルに関わるものなので、 ~/.zshrc に移動します。

`$PS1` 変数をいじれば bash のようにプロンプトを改造できます。
(他にも関連要素が色々ありますが、ここでは割愛します。)
ただし書式が異なるのでコピペではうまく動かない場合があります。

詳細は "zsh prompt" でググるか `man zshmisc` をお読みください。

これまた誰かのコードをコピペしてセットアップしてた場合、元の作者さんが何か知ってるかもしれません。

```zsh:~/.zshrc
# git in Xcode command-line tools プロンプト
# 現物見ると複製して使ってねって書いてありますが、僕は直接参照してます
source /Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-prompt.sh
precmd() {
    # Git プロンプトを使う場合はこんな感じで特殊な記法が提供されてます
    __git_ps1 %m '%# ' ' (%s)'
}
```

## コマンド履歴を設定する

macOS の bash は何もしなくてもコマンド履歴を保存してくれましたが、**zsh の場合は設定を書く必要があります。**…面倒ですね。

対話的シェルにしか関係ない設定なので、これも ~/.zshrc に書きます。

```zsh:~/.zshrc
# コマンド履歴の保存に関する設定
HISTFILE=~/.zhistory           # 履歴の場所。なければ勝手に作られ chmod 600 されます
SAVEHIST=500                   # ファイルに保存する件数。bashのデフォルトと同じにしてみました
HISTSIZE=$(($SAVEHIST * 2))    # メモリに蓄える件数。ここではzshの文法で「$SAVEHISTの2倍」
setopt HIST_EXPIRE_DUPS_FIRST  # 単に古いコマンドより、重複コマンドの方を優先的に忘れるオプション
```

# まとめ

以下の点だけ注意してればほぼコピペで何とかなります。

- .bash_profile に .bashrc をインクルードしてる場合、 その前処理を .zprofile、後処理を .zlogin に移行します。
- コマンド補完はコピペせずに書き下ろす覚悟で臨みましょう。
- bash のコマンド履歴を設定した覚えがない場合、 zsh 移行時にコマンド履歴を設定しましょう。

# 参考文献

脚注にまとめました。