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