📝

稀によく見る`#!/bin/sh`の役割

2023/01/13に公開

#!/bin/bashとか#!/bin/shって感じの記述をたまに見ます。どう言う意味なのか調べ、自分なりに色々検証してみたらおおよその見当が付いたのでメモしていきます。

結論

#!(shebangと呼ばれます。他の流派もある様ですが)はコマンドとして解釈される。

例えばwater.rsというファイルがあったとしましょう。

water.rs
#!rustc

int main(){ println!("{}",1+2) }

このファイルをchmod u+xしてexecutableにします。次に

> ./water.rs

を実行すると裏で

> rustc ./water.rs

が実行されます。最終的に./water(実行可能ファイル)が生成されます。

詳細

主にshell scriptなんかで見かける#!(以下、shebangと呼びます)。たまにpythonでも

#!/usr/bin/python3
...

の記述を目にします。まず前提としてshebangは1行目に記述され、shebangのあるファイルはexecutableであることが期待されます。ではそれらのファイルを実行すると何が起こるのでしょうか。順を追って見ていきましょう。ぜひ読んでる方も一緒に試して見てください。おぉ! ってなりますよ😊

./shell.swift

cwd~/suwifutoとし、~/suwifuto内にshell.swiftがあるとします。

言語はswiftでなくてもinterpreterがある言語ならなんだっていいです.

shell.swiftに以下の様なプログラムを書いて見ましょう。

shell.swift
#!swift
// if you use bash, replace `swift` to absolute path to the `swift`
print("I would rather goose.")

次に

> chmod u+x shell.swift

を実行しshell.swiftを実行可能にします。次に、shell.swiftを実行...する前に

実行すると何が起こると思いますか? まぁメタ的にI would rather goose.と出力されそうなんですが、では仮にそうだったとしてどういう手順でそうなるのでしょうか?

[thinking time..]

と、しばらく考えた上で実行して見ましょう。

> ./shell.swift
I would rather goose.

どうだったでしょうか?おそらく予想通り I would rather goose.と出力されたと思います。
ではもう一歩踏み込んで

shell.swift
#!$(which swift)
print("I would rather goose.")

とするとどうなるでしょうか?

[thinking time..]

$(which swift)swiftコマンドへのパスを返します。なので先ほどと同様にプログラムが実行されI would rather goose.と出力されると自分は予想していました。では実行して見ましょう。

> ./shell.swift
zsh: ./shell.swift: bad interpreter: $(which: no such file or directory

何やらzshから怒られました。no such file or directoryとあります。#!/bin/bashなどの記述から分かる様になんらかのpathを渡すんだなと理解できます。次にbad interpreter: $(whichとあるようにwhitespaceの前で読み込みが止まっています。これもpathには通常空白は含まれないからと考えるとしっくりきます。..が#!/usr/bin/env pythonみたいな記法だとちゃんと読み込まれます。

実は#!clang --versionみたいにしてもきちんと動作します

つまりshebangはshellにpathを渡す構文なんだ!というと少し語弊があるわけです。初めに、#!swiftとしてもきちんと実行されましたしね。ではshebangがスクリプト言語でよく見受けられることからもう少し踏み込んで、shebangはshellにどのinterpreterを使うかを教えるんだ と解釈して見ましょう。

ここで、

shell.swift
#!swiftc
print("I would rather goose.")

としてみると、どう出力されると思いますか? まぁ皆さんメタ読みでもう分かってると思うのでわからないので実行して見ましょう!

> ./shell.swift

案の定なんと、何も出力されません。そして同じフォルダにshellという実行可能ファイルが作られていることがわかります。

(余談)
swiftを説明に使うのに違和感を覚えた方もいると思います。いやそこはshell scriptかpythonだろ!って。
swiftを説明に使った理由はinterpreterもcompilerもどっちもある言語ってなんだろう と考えた時に真っ先に出てきたのがswiftだったからです。

これらの検証からshebangの正体がなんとなく分かってきましたね。

#!の正体

hoge.hageというファイルがあったとする。

hoge.hage
#!aiueo
etcetera..

この時、./hoge.hageを実行すると裏で

> aiueo ./hoge.hage

が実行される

蛇足

そうとわかれば色々と遊べそうです。例えば

dog.txt
#!cat
I'm dog

とすれば

> ./dog.txt
#!cat
I'm dog

となります。同じディレクトリに

cat.lua
-- where is bat?
answer.c
// here

を追加し

> ./dog.txt cat.lua answer.c

を実行すると

> ./dog.txt
#!cat
I'm dog
-- where is bat?
// here

となります。

先ほどのshell.swift

shell.swift
#!swiftc -v
print("I would rather goose.")

としてやると

❯ ./shell.swift
Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: arm64-apple-darwin22.2.0
/Library/Developer/CommandLineTools/usr/bin/swift-frontend -frontend -c -primary-file ./shell.swift -target arm64-apple-darwin22.2.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -color-diagnostics -target-sdk-version 13.1 -module-name shell -o /var/folders/_2/q8xq5ndj64l04n3_17jnwvq40000gn/T/shell-c50ecb.o
/Library/Developer/CommandLineTools/usr/bin/ld /var/folders/_2/q8xq5ndj64l04n3_17jnwvq40000gn/T/shell-c50ecb.o /Library/Developer/CommandLineTools/usr/lib/swift/clang/lib/darwin/libclang_rt.osx.a -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -lobjc -lSystem -arch arm64 -L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift -platform_version macos 13.0.0 13.1.0 -no_objc_category_merging -o shell

swiftc -v shell.swiftとした場合と同様のことが起こります。

当然ですが

aiu
#!hoge
> ./aiu
zsh: ./aiu: bad interpreter: hoge: no such file or directory

の様に、存在しない(or pathが通っていない)コマンドにするとエラーになります。

では最後にちょっとしたクイズです。

shell.swift
#!shell.swift
print("are you swift?")

とした時、このファイルを実行するとどうなるでしょう? 自分はなんかinvalid argumentみたいな警告が出るのかな? と考えていました。では実行して見ましょう。

> ./shell.swift
zsh: exec format error: ./shell.swift

いや分かるかい

(おしまい)

Discussion