シェルスクリプトのシバン(Shebang)にシェル以外のコマンドを記述したらどうなるのか?

2020-09-30Bash

はじめに

Unix上で動作するスクリプトは、1行目に #! から始まる1行を記述します。
これによりスクリプトの実行系を認識させることができます。

シェルスクリプトの場合は #!/bin/sh#!/usr/bin/bash#!/usr/bin/env bash と記述します。

Rubyスクリプトの場合は #!/usr/bin/ruby#!/usr/bin/env ruby と記述することが多いでしょう。

この1行目の記述のことを「 シバン 」とか「 Shebang 」と呼びますが、今ひとつこのあたりの仕組が整理できてなかったため、まとめてみました。

検証環境

$ uname -moi
x86_64 MacBookPro16,1 Darwin

$ bash -version | head -n 1
GNU bash, バージョン 5.0.17(1)-release (x86_64-apple-darwin19.4.0)

(実験) #!/bin/rm と記述し実行してみる

突然ですが、 シバン の挙動を理解するために実験をしてみます。

一行目に #!/bin/rm とだけ記述されたシェルスクリプトを作成してみます。

# 作成
$ cat <<EOF > test.sh
#!/bin/rm
EOF

# 内容が正しいかを確認してみる
$ cat test.sh
#!/bin/rm

# 実行権限を付与
$ chmod +x test.sh

# カレントディレクトリを確認
$ ls -la
total 8
drwxr-xr-x 3 root root   96 Sep 27 13:46 .
drwxr-xr-x 1 root root 4096 Sep 27 13:34 ..
-rwx--x--x 1 root root   10 Sep 27 13:47 test.sh

作成したスクリプトを以下のように実行してみます。

$ ./test.sh

実行後、カレントディレクトリを確認してみると、 test.sh というスクリプトがなくなっていることがわかります。

$ ls -la
total 4
drwxr-xr-x 2 root root   64 Sep 27 13:48 .
drwxr-xr-x 1 root root 4096 Sep 27 13:34 ..

これはなぜでしょう?

(実験) #!/bin/echo と記述し実行してみる

シバン付きのスクリプトは、 スクリプト名を引数として実行される ようです。
先の実験では、 ./test.sh を実行した結果、 /bin/rm ./test.sh として解釈されたというわけです。

追加の実験をしてみます。

一行目に #!/bin/echo とだけ記述されたシェルスクリプトを作成してみます。

# 作成
$ cat <<EOF > test.sh
#!/bin/echo
EOF

# 内容が正しいかを確認してみる
$ cat test.sh
#!/bin/echo

# 実行権限を付与
$ chmod +x test.sh

作成したスクリプトを、いくつかのコマンドライン引数付きで実行してみます。

$ ./test.sh aa bb ccc
./test.sh aa bb ccc

ここから、以下のようなコマンドが実行されたことがわかります。

$ /bin/echo ./test.sh aa bb ccc

改めて #!/bin/bash の意味を考える

改めて #!/bin/bash の意味を考えてみます。
例えば、 シバン付きの test.sh スクリプトを以下のように実行した場合を考えてみます。

$ ./test.sh aa bb ccc

これは、以下のようなコマンドが実行されることとなります。

/bin/bash ./test.sh aa bb ccc

このように整理できると、シェルスクリプトの中で ${0} という組み込み変数が 実行されたスクリプトファイルのパス を示している仕組みが理解できるようになります。

御存知の通り、コマンドライン引数を表す変数の値は、 ${1}aa${2}bb${3}ccc となります。

(実験) #!/bin/echo first と記述し実行してみる

#!/usr/bin/env bash の意味についても考えてみたいと思います。

その前に実験です。
先程の echo コマンドをシバンとして設定したスクリプトを多少触ってみます。
一行目に #!/bin/echo first と記述されたシェルスクリプトを作成してみます。

$ cat <<EOF > test.sh
#!/bin/echo first
EOF

$ chmod +x test.sh

以下のように引数を付与して実行してみます。

$ ./test.sh xx yy zz

実行結果は以下の通りとなります。

first ./test.sh xx yy zz

これはつまり、 /bin/echo first ./test.sh xx yy zz というコマンドが実行されたと解釈できます。

#!/usr/bin/env bash も同様です。

#!/usr/bin/env bash がシバンに設定された test.sh スクリプトを実行すると、 /usr/bin/env bash ./test.sh というコマンドが実行されることとなります。

補足: env コマンドとは?

env コマンドを通すと $PATH 環境変数の設定を使って bash コマンドを探すため、 /bin/bash にコマンドが配置されていない環境でも動作しますし、 bash が複数インストールされている場合(たとえばバージョン違い)にも、最適なものを検索してくれます。

ひとこと

多少はシバンの仕組みが整理できたでしょうか?

2020-09-30Bash