Macでシェルスクリプトを書く人へ 「シェルはbashからzshに変わっていません！今でもbashで動くんだよ！」

## TL;DR

Mac のシェルは macOS 10.15 Catalina で bash から zsh に変わったと一般に言われていますが、インストールされているシェルはバージョン番号の違いを除いて以前とほとんど変わっていません。

- `/bin/sh` は POSIX モードで bash (`/bin/bash`) を起動します
- `/bin/bash` は bash 3.2.57 （正確には Apple 版 bash-123.40.1）です
- `/bin/zsh` は zsh 5.8 です

その他に以下のシェルも標準でインストールされています。

- `/bin/csh`, `/bin/tcsh` は tcsh 6.21.00 です
- `/bin/ksh` は ksh 93u+ で主に商用 UNIX でよく使用されていた POSIX シェルです。
- `/bin/dash` は純粋な POSIX シェルに一番近くて高速なシェルです<br>ただしインタラクティブシェルには向いていません

macOS 10.15 Catalina で変更になったのは新規作成したユーザーがデフォルトで使うインタラクティブシェル（ターミナルで使うシェル）が zsh になった事と macOS 標準の古い bash をインタラクティブシェルとして使い続けてる人に警告が表示されるようになっただけです。**シェルが変わったのではなく、デフォルトで使用するシェルが変更になっただけです。**

また、インタラクティブシェルで使用するシェルと、シェルスクリプトで使用するシェルは別々であることに注意してください。インタラクティブシェルとして使うシェルは設定で決まります。シェルスクリプトで使用するシェルは、シェルスクリプトファイルの一行目のシバン（`#!` で始まる行）で指定したシェルです。`/bin/sh` や `/bin/bash`が置き換わったわけではないので**以前の macOS 上の bash で動いていたシェルスクリプトを zsh 用に書き換える必要はありません**。シバンで `/bin/sh` や `/bin/bash` を指定しているシェルスクリプトはインタラクティブシェルを zsh に変更したとしても同じように bash で動きます。（ただし一般論として `/bin/sh` は bash であるとは限らないので、シバンに `#!/bin/sh` を使うのであれば POSIX シェルに準拠させましょう。）

注意 もちろん現時点（macOS 11.4 Big Sur）での情報です。将来の macOS のバージョンでは変わる可能性はあります。

## はじめに

Mac のシェルは bash から zsh に変わってはいません。Mac の「**システムシェル**」は今も昔も bash 3.2.57 のままです。macOS 10.15 Catalina から zsh に変わったと聞いた？ それは新規作成したユーザーの「**インタラクティブシェル**」がデフォルトで bash から zsh を使うように変わったというだけのことです。もともとインタラクティブシェルはユーザーが自由に変更可能なものです。そして**これはシェルスクリプトを動かすシェルの話ではありません。**今までインタラクティブシェルとして bash を使っていた人に対しては zsh に変更しろと案内が表示されますが、変更した所で**シェルスクリプトを動かすシェルは bash のまま**です。だから（macOS 上で） bash を前提に書かれたシェルスクリプトであっても修正せずに問題なく動きます。

シェルスクリプト製プログラムに対するバグ報告（GitHub の Issue 等）でも「○○が動きまん。シェルは zsh を使っています。」などと書かれていたりするのですが、（もちろん書いてあったほうが良いのですが）実際の所、ユーザーが使ってるインタラクティブシェルはシェルスクリプト製プログラムの動作にはほとんど関係ありません。（rbenv のようにシェル関数を定義する仕組みもあるので絶対ではありませんが。）

シェルの初心者であればこのように勘違いするのは仕方ないことだと思いますが、頭が痛いことに某シェルスクリプト入門書でさえ、Mac のデフォルトシェルが zsh に変わったから bash 前提で書いたシェルスクリプトが動かなくなって書き換えなければならなくなる（だから POSIX 準拠でシェルスクリプトを書け）とか平気で間違いを書いているのだから困ったものです。（自説が正しいと主張を通すためにあえて嘘を書いてる可能性も高いですが、他にも多数の間違いがあるので単に著者がシェルスクリプトに詳しくないだけでしょう。）

## システムシェルとインタラクティブシェル

**システムシェル**のパスは `/bin/sh` です。macOS では（[最初期を除いて](https://www.in-ulm.de/~mascheck/various/shells/#macosx10.0)）古くから bash が使われており最新の macOS では bash 3.2.57 が使われています。最新の bash は 5.1.8 であるため かなり古い bash ですが、bash 4.0 からライセンスがそれまでの GPLv2 から GPLv3 へと変更されており、それが理由でアップデートされていないという説が有力です。

ターミナル（黒い画面）を起動した時に実行される**インタラクティブシェル**のパスは macOS 10.15 Catalina より前はデフォルトの設定で `/bin/bash` が使われており、今は `/bin/zsh` です。昔からシステムシェルとインタラクティブシェルのパスは違います。パスが異なるだけで両方とも同じ bash ・・・ というわけではありません。 `/bin/sh` で起動した bash は POSIX モードの bash です。

さらに Apple 版の bash は Linux などの bash と動きが異なる部分があります。具体的には `/bin/sh -c "echo -n hello"` の出力結果が `-n hello` となります。この動作は [POSIX echo](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html) の XSI 拡張オプションに準拠した動作で、これに準拠してないと UNIX 03 を名乗れない（[参考](http://manpages.ubuntu.com/manpages/bionic/ja/man7/standards.7.html)）ため変更しているのだと思われます。ちなみに Apple 版 の bash のソースコードは[ここ](https://opensource.apple.com/tarballs/bash/)で配布されています。

まとめるとシステムシェルのパスは `/bin/sh`、デフォルトのインタラクティブシェルのパスは以前は `/bin/bash` で今は `/bin/zsh` というわけです。

## インタラクティブシェルについて

ターミナルを起動した時に実行されるシェルで、ユーザーが自由に変更することができます。macOS に標準でインストールされているシェルのパスは `/etc/shells` に記述されており、この中からインタラクティブシェルとして使用するシェルを選ぶことができます。またこの一覧に追加すれば、追加でインストールしたその他のシェル（最新の bash や fish 等）も自由にインタラクティブシェルとして使用することができます。

```console
$ cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
```

ユーザーが設定しているインタラクティブシェルは環境変数 `SHELL` で調べることができます。私の場合は zsh なので zsh のパスが表示されます。

```console
$ echo $SHELL
/bin/zsh
```

この環境変数 `SHELL` は現在使用しているシェルではなく、ユーザーが設定しているインタラクティブシェルなので注意してください。例えば私が zsh 上から新しく bash を起動しても `SHELL` の内容は zsh のままです。現在使用しているシェルを知りたい場合は `ps $$` を実行してください。

設定しているインタラクティブシェルを別のシェルに変更する場合は `chsh` コマンドを使用します。例えば zsh に変更する場合は `chsh -s /bin/zsh` です。ただしターミナルソフトによってはデフォルトのインタラクティブシェルの設定を無視してターミナルソフト側の設定が優先されることがあるので注意してください。

そして繰り返しますが、**インタラクティブシェルの設定はシェルスクリプトを動かすシェルと一切関係がありません**。

## シェルスクリプトを動かすシェルとは？

シェルスクリプトを動かすシェルはスクリプトファイルに書いてあるシバン（shebang）によって決まります。シバンとはスクリプトファイルの一行目に書いてある `#!/bin/sh` や `#/usr/bin/env bash` などのことで `#!` で始まる文字列のことです（ただのコメントではありません）。シェルスクリプトはここで指定しているシェルで実行されます。（注意 シバンで指定するコマンドはシェルである必要はありません。例えば スクリプトファイルが Python スクリプトであれば `#!/usr/bin/env python` と書きます。）

```sh:script.sh
#!/bin/sh
# ↑ シバンが /bin/sh なので
ps $$
```

```sh
$ chmod +x ./script.sh # 実行権限をつけて置くこと
$ ./script.sh
  PID   TT  STAT      TIME COMMAND
86316 s001  S+     0:00.00 /bin/sh ./script.sh # ← /bin/sh が使われている
```

ただし（実行権限がついていなくてファイル名指定で実行できないからと）以下のような実行の仕方をした場合はシバンは無視されて bash で実行されるのでので注意してください。この方法でシェルスクリプトを実行するのはシバンを意図的に無視したい場合だけです。

```sh
bash ./script.sh
```

### もしシバンを書いていなかったら？

先に結論を言うと、そんなことはしないでください。もしシバンを書いてないシェルスクリプトを実行した場合、それがどのシェルで動くかは実行したシェル次第です。実際にどのシェルで動くかは「[シバン shebang がないシェルスクリプトはどのシェルで動くかわからない（からちゃんと書いとけ）](https://qiita.com/ko1nksm/items/1dd4937e7f0cc2ba3f75)」を参照してください。

### もう少し細かい話

`/bin/sh` は bash と書きましたが、実際には bash、dash、zsh のいずれかを再実行するプログラムになっています。`man sh` は短いので全文引用します。

```
SH(1)                     BSD General Commands Manual                    SH(1)

NAME
     sh -- POSIX-compliant command interpreter

SYNOPSIS
     sh [options]

DESCRIPTION
     sh is a POSIX-compliant command interpreter (shell). It is implemented
     by re-execing as either bash(1), dash(1), or zsh(1) as determined by
     the symbolic link located at /private/var/select/sh.
     If /private/var/select/sh does not exist or does not point to a valid
     shell, sh will use one of the supported shells.

FILES
     /private/var/select/sh

     $HOME/.profile

     /etc/profile

SEE ALSO
     bash(1), dash(1), ksh(1), tcsh(1), zsh(1)

BSD                          February 8, 2019                          BSD
``` 

`/bin/sh` は実際には `/private/var/select/sh` を実行するコマンドになっており `/private/var/select/sh` は `/bin/bash` へのシンボリックリンクになっています。このシンボリックリンクを変更すると別のシェルを `/bin/sh` として使用することができます（試していません）。なお https://support.apple.com/kb/HT208050 では同様のことをするには `/var/select/sh` を変更してくださいと書いてありますが`/var` は `/private/var` へのシンボリックリンクなので同じ意味です。

## macOS のシェルスクリプトはどのシェル用で書くべきか？

シェルスクリプトをどのシェル用で書くかは基本的に好みです。移植性（Linux で動かす）などを考慮する必要がないのであれば、好きなシェル用で書いて構いませんが、シバンには注意してください。

シバンに `#!/bin/sh` を使用した場合は（bash）ではなく POSIX シェル準拠で書くことをおすすめします。macOS では `/bin/sh` は bash ですが他の環境では bash とは限りません。例えば Ubuntu / Debian では dash が使われています（dash は 純粋な POSIX シェルに近いシェルで拡張機能を殆ど持っていないシェル）。つまり `/bin/sh` は 純粋な POSIX シェルの機能が使えるということは想定できても、それ以外の機能が使えるという想定はできないものなのです。

シェルスクリプトで bash の機能を使用したい場合はシバンに `#!/bin/bash` を使用します。これを使用している限り以前の macOS と同じシェルスクリプトがそのまま動きます。その代わり macOS では バージョンがかなり古いです。他の環境では新しい bash が使われていることでしょう。もし macOS で最新版の bash を使いたいのであれば、シバンに `#!/usr/bin/env bash` を使用して Homebrew などで bash をインストールします。

シェルスクリプトを zsh で実行したい場合は `#!/bin/zsh` もしくは `#!/usr/bin/env zsh` を使用します。bash の場合と同様に macOS のシステムでインストールされているバージョンを使いたいのであれば `#!/bin/zsh`。Homebrew でインストールしたバージョンを使いたいなら `#!/usr/bin/env zsh` です。

## インタラクティブシェルを zsh に変更すると何が変わるのか？

すでに書いているようにインタラクティブシェルを zsh （やその他のシェル）に変更したとしてもシェルスクリプトはシバンを書いていればそのシバンで指定されたシェルで動きます。シバンを書いてない場合であっても zsh の場合は sh で実行するので何も変わらないでしょう。

違いはターミナルのインタラクティブシェル（zsh）で実行する場合、とシェルスクリプト（bash）から実行する場合で、挙動が変わることがあるという所です。例えば bash では `[]` という文字をコマンドの引数として使う場合にクォートで括る必要がありません。しかし zsh では括らなければいけません。

```sh
# bash の場合
$ echo hello[world]
hello[world]

# zsh の場合
% echo hello[world]
zsh: no matches found: hello[world]

% echo "hello[world]"
hello[world]

# rake では引数に [] を使うため、bash 用のサンプルコードが動かない
% rake new_post['title']
zsh: no matches found: new_post[title]

# zsh ではクォートで括る必要がある
% rake "new_post['title']"
```

同じコマンドでも実行結果が異なる場合があります。

```sh
# zsh の場合
echo "\033[31m RED \033[m" # 赤で RED と表示される

# bash の場合
echo "\033[31m RED \033[m" # \033[31m RED \033[m と表示される
```

これは `echo` コマンドが外部コマンドではなくシェルに内蔵されたビルトインコマンドだからです。シェルの機能を使っているためインタラクティブシェルでは zsh の `echo` コマンドが、シェルスクリプトでは bash の `echo` コマンドが使われてしまうわけです。もしどのコマンドがシェルビルトインであるかどうか知りたい場合は `type` コマンドで調べることができます。

```sh
# bash の場合
$ type [[
[[ is a shell keyword

# zsh の場合（[ はクォートで括る必要があることに注意）
% type "[["
[[ is a reserved word

# bash の場合
$ type which
which is /usr/bin/which

# zsh の場合
% type which
which is a shell builtin
```

シェルスクリプトに慣れていない人がシェルスクリプトを書く時、ターミナル上で試しにコマンドを打って実験してからシェルスクリプトに書き写すということはよくやると思います。そうでない人でもターミナルからワンライナーで書いたコードをシェルスクリプトに書き写すこともあると思います。そういう場合にターミナル上では動いたのにシェルスクリプトにすると動かないなどということが起こりえます。ターミナルから実行するのとシェルスクリプトから実行するのとでは、似ているけど違うシェルで実行しているということに注意してください。

### シェルの設定ファイルには注意

インタラクティブシェルを変更してもシェルスクリプトで使うシェルは変わりませんが、シェルの設定ファイルは該当するシェルで書かなければいけません。ファイル名からわかるかと思いますが `.bash_profile` や `.bashrc` は bash 専用なので zsh を使う場合は代わりに `.zprofile` や `.zshrc` に設定を書かなければいけません。これらはシェルスクリプトとして実行されるのではなく（だからシバンは不要）使用するインタラクティブシェルで読み込まれるものなので、zsh 用の設定ファイルは zsh で書く必要があります。

## macOS で bash を使い続けたい人へ

インタラクティブシェルで使うシェルとシェルスクリプトで使うシェルが違うのは嫌だ。シェルの設定をバリバリにカスタマイズしてるから今までと同じ bash を使い続けたい。という人もいるでしょう。その場合に bash を使い続けることは可能です。macOS 標準の（古い）bash を使う方法と、最新の bash を使う方法を紹介します。

### macOS 標準の bash を使いたい

あまりおすすめはしませんが、新しい bash をインストールするのが面倒という人には良いかもしれません。この場合に問題になるのが zsh に変更してくれという警告メッセージです。このメッセージはホームディレクトリの `.bash_profile` または `.profile` に以下の一行を追加するだけで消すことができます。

```sh
export BASH_SILENCE_DEPRECATION_WARNING=1
```

### 最新の bash を使いたい

最新の bash が使えないのは Apple の都合でしか無いので、他の OS に合わせて最新の bash を導入するのも手です。ちなみに現時点での最新の bash のバージョンは 5.1.8 です。

最新の bash は Homebrew などでインストールすると良いでしょう。その後 `/etc/shells` にその bash のパス （おそらく `/usr/local/bin/bash` か `/opt/homebrew/bin/bash` ですが `command -v bash` で確認してください）を追加し `chsh -s` で切り替えるだけです。

シェルスクリプトで最新の bash を使う場合はシバンを `#!/usr/bin/env bash` にするのを忘れないでください。`#!/bin/sh` や `#!/bin/bash` では macOS 標準の古い bash が使われてしまいます。

## 将来の macOS で `/bin/sh` が zsh に変わったりしないの？

`/private/var/select/sh` による `/bin/sh` 切り替え機能を用意していることから将来的に `/bin/sh` を bash 以外に変える可能性はあると思っています。しかし zsh に変える可能性は低いと思っています。そもそも `/bin/sh` が POSIX シェルに準拠した機能だけを使えるシェルという前提であれば現状の bash 3.2.57 でも十分要件を満たしています。また zsh を `/bin/sh` として実行すると POSIX モードが有効になるため POSIX シェルとの互換性は向上しますが zsh 特有の機能が無効になったり bash 互換になったりはしません。本来使うべきではない bash の機能の代わりに、使うべきではない zsh の機能が使えるようになるだけなので `/bin/sh` を zsh に変更するメリットがあるとは思えません。

変わる可能性があるとするなら dash でしょう。`/private/var/select/sh` で変更可能なシェルの対象となっているのはその理由の一つです。dash は純粋な POSIX シェルに近いシェルで拡張機能をほとんど実装しておらず実行速度も高速です。実際 Ubuntu / Debian の `/bin/sh` は 2006 年前後に bash から dash へと変更さました。しかしそれでも互換性を考えると可能性は低いでしょう。bash よりも dash の方が機能が少ないので dash に変更すると既存のシェルスクリプトが動かなくなる可能性があります。実行速度やメンテナンスの点から dash に変えるメリットがあるとは言え、今の bash を Apple 独自でメンテナンスし続けるのが困難にならない限り変える必要があるとは思えません。POSIX シェルは大きく変更されることはないので bash のメンテナンスもさほど大変な作業ではないと思います。

### もし将来 `/bin/sh` が zsh や dash に変わったら？

その時はその時で記事を書くと思いますが、シバンを `#!/bin/bash` に変更してファイルの初めの方（シバンの後）に `set -o posix` と書けば今まで通り古い bash で POSIX モードでシェルスクリプトが動くはずです。大した作業にはなりません。

## さいごに

インタラクティブシェルとシェルスクリプトを動かすシェルは違います。インタラクティブシェルは自由に好きなシェルを使うことができます。zsh や POSIX シェルと互換性がないシェル（fish 等）に乗り換えることもできます。だからといってシェルスクリプトまで別のシェルで書かないといけないということにはなりません。インタラクティブシェルは fish を使うけど、シェルスクリプトは Linux でも動かしたりするから POSIX 準拠のシェルスクリプトで書くというのもよくある使い方です。

二種類のシェルスクリプト言語を覚えて使い分けるのが嫌だというのなら統一した方が楽だと思いますが、それは使う人の自由です。ちなみに私はインタラクティブシェルとしては（歴史的経緯で）macOSでは zsh、Linux では bash を使っておりシェルスクリプトは（必要な場合は各シェルの拡張機能を使いますが）原則として POSIX 準拠で書いています。インタラクティブシェルのカスタマイズはほとんどしてないのでインタラクティブシェルとしての bash や zsh の機能は使っていません。こういう使い方なので実は私はインタラクティブシェルを他のシェルに変更したとしてもあまり困らなかったりします。必要がないのでやらないだけです。

他の環境との移植性を考えるとシェルスクリプトは POSIX シェル準拠またはデファクトスタンダードに近いと言える bash で書くのが良いと思いますが、インタラクティブシェルは自由に変更することができます。世の中にはいろいろなシェルがあります。その中には特徴的な機能を持ったシェルもあります。興味がある方はいろんなシェルを試してみるのも良いでしょう。

**関連記事**

- [新macOS Catalina が zsh を標準にするって聞いたので Mojave のまま移行してみた](https://qiita.com/tkooler_lufar/items/66e8fa451185e812a33a)
- [Macでbashからzshへお引越しする方法](https://qiita.com/AirBeans5956/items/6a00443c6118d7d3f5f4)
- [Mac のデフォルトシェルが bash から zsh に変更された機に乗じて乗り換える手順](https://qiita.com/normalsalt/items/5b71fbd03721800de4a0)
- [MacOS Catalinaでbashをzshにせよという警告文を消す方法](https://qiita.com/waka424/items/bc77b6e8bd4f25760e58)