双六工場日誌

平凡な日常を淡々と綴ります。

/bin/sh と /bin/bash の違い

みなさん、shebang書いてますか!

Shebangというのは、スクリプトの最初の一行目に書く、「#!/bin/sh」とか「#!/usr/bin/perl」とかそういうあれです。詳しくはWikipediaさん(シバン (Unix))に聞いてくださいまし。

Twitter見てると、「そもそもこれにshebangなんて名前がついてるの知らなかったよ」という発言も見る不憫な子ではあるのですが、そこに何が書いてあるかで実は動作が違うよってのが今日の本題です。それで、はまっていたのを最近見て、まああまりここで引っかかる人はいないと思いつつ、この点を書いた情報を見ないのでまとめてみました。*1

今日取り上げるのは、bashがデフォルト設定になっているLinuxでの「#!/bin/sh」と「#!/bin/bash」のお話。確認はCentOS5, 6で行なっています。

さて、上記の環境の場合、「/bin/sh」は「/bin/bash」へのシンボリックリンクになっています。ということは、どちらを書いても同じ、、、はず?

ということで、以下のスクリプトを試しに実行してみます。実行する内容は「. ./file_not_found」*2だけで、file_not_foundという外部スクリプトの中身を読み込んで実行しようとするものです。ただ「file_not_found」は、その名の通り、実際には存在しないファイルとなっており、ここでエラーが起きます。

#!/bin/sh

$ cat shtest.sh
#!/bin/sh

. ./file_not_found

echo "ここまで実行したよー!"

#!/bin/bash

$ cat shtest.sh
#!/bin/bash

. ./file_not_found

echo "ここまで実行したよー!"

で実行してみると、

$ ./shtest.sh
./shtest.sh: line 3: ./file_not_found: そのようなファイルやディレクトリはありません
$ ./bashtest.sh
./bashtest.sh: line 3: ./file_not_found: そのようなファイルやディレクトリはありません
ここまで実行したよー!

ということで結果が違う! 前者は「.」でのファイルの読み込みでスクリプトの実行が停止しているのに対し、後者はその先のスクリプトも実行されています。そう、「/bin/sh」でスクリプトを動かすのと「/bin/bash」でスクリプトを動かすのでは結果が違うのです。

あまり引っ張っても仕方ないので、ネタばらしすると、この動作はbashのマニュアルにも記載されているPOSIX互換モードによるものです。manによれば、

sh という名前で bash を起動すると、 bash は古くからある sh の起動動作をできるだけ真似しようとします。
man bash より

と書かれています。実際に「set -o」にてスクリプトの実行モードも確認してみます。

$ cat shcheck.sh
#!/bin/sh
set -o

$ ./shcheck.sh
allexport       off
braceexpand     on
emacs           off
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      off
history         off
ignoreeof       off
interactive-comments    on
keyword         off
monitor         off
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           on ←ここが”on"になっている。
privileged      off
verbose         off
vi              off
xtrace          off
$ cat bashcheck.sh
#!/bin/bash
set -o

$ ./bashcheck.sh
allexport       off
braceexpand     on
emacs           off
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      off
history         off
ignoreeof       off
interactive-comments    on
keyword         off
monitor         off
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           off ←ここが”off"になっている。
privileged      off
verbose         off
vi              off
xtrace          off

と前者はPOSIXモードがonになっていることがわかります。


この2つの違いは、ほとんどの場合に動作に影響はないですが、上に上げた「.」でのファイル読み込み等で一部で動作の違いを産みます。たとえば、以下のようなbash固有の書き方を使っている場合、通常は動作しますが、POSIXモードだとエラーになります。

$ cat shtest2.sh
#!/bin/sh
# コマンドの実行結果をファイル入力のように扱ってコマンド(diff)に渡す
diff <(ls /home) <(ls /homuhomu)

$ sh shtest2.sh
./shtest2.sh: line 3: 期待してない token `(' のあたりにシンタックスエラー
./shtest2.sh: line 3: `diff <(ls /home) <(ls /homuhomu)'

$ bash shtest2.sh
1d0
< data

ただ、POSIXモードは単純にPOSIXシェルの動きとなるわけではなく、例えばbashの配列などは問題なく動くため、POSIXシェルとbashで動作が異なる部分を選択的にPOSIXに合わせるような動きになるようです。たとえば以下は問題なく動作します。

$ cat shtest3.sh
#!/bin/sh
ARRAY=("1" "2" "3")
echo ${ARRAY[1]}

$ ./shtest3.sh
2

と、shebangの書き方で動作が変わる点をまとめて見ました。もしここで引っかかるようなマニアックなスクリプトを書いている方がいたら、参考にしてくださいまし。

*1:manにはちゃんと書いてあるんですけどね。

*2:. は source コマンドの別名で、外部ファイルの中身を読み込んで、そのシェルの一部として取り込んで実行するコマンド。詳しくはman bash 参照