稀によく見る`#!/bin/sh`の役割
#!/bin/bash
とか#!/bin/sh
って感じの記述をたまに見ます。どう言う意味なのか調べ、自分なりに色々検証してみたらおおよその見当が付いたのでメモしていきます。
結論
#!
(shebangと呼ばれます。他の流派もある様ですが)はコマンドとして解釈される。
例えば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
に以下の様なプログラムを書いて見ましょう。
#!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.
と出力されたと思います。
ではもう一歩踏み込んで
#!$(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を使うかを教えるんだ と解釈して見ましょう。
ここで、
#!swiftc
print("I would rather goose.")
としてみると、どう出力されると思いますか? まぁ皆さんメタ読みでもう分かってると思うのでわからないので実行して見ましょう!
> ./shell.swift
案の定なんと、何も出力されません。そして同じフォルダにshell
という実行可能ファイルが作られていることがわかります。
(余談)
swiftを説明に使うのに違和感を覚えた方もいると思います。いやそこはshell scriptかpythonだろ!って。
swiftを説明に使った理由はinterpreterもcompilerもどっちもある言語ってなんだろう と考えた時に真っ先に出てきたのがswiftだったからです。
これらの検証からshebangの正体がなんとなく分かってきましたね。
#!
の正体
hoge.hage
というファイルがあったとする。
#!aiueo
etcetera..
この時、./hoge.hage
を実行すると裏で
> aiueo ./hoge.hage
が実行される
蛇足
そうとわかれば色々と遊べそうです。例えば
#!cat
I'm dog
とすれば
> ./dog.txt
#!cat
I'm dog
となります。同じディレクトリに
-- where is bat?
// here
を追加し
> ./dog.txt cat.lua answer.c
を実行すると
> ./dog.txt
#!cat
I'm dog
-- where is bat?
// here
となります。
先ほどの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
とした場合と同様のことが起こります。
当然ですが
#!hoge
> ./aiu
zsh: ./aiu: bad interpreter: hoge: no such file or directory
の様に、存在しない(or pathが通っていない)コマンドにするとエラーになります。
では最後にちょっとしたクイズです。
#!shell.swift
print("are you swift?")
とした時、このファイルを実行するとどうなるでしょう? 自分はなんかinvalid argumentみたいな警告が出るのかな? と考えていました。では実行して見ましょう。
> ./shell.swift
zsh: exec format error: ./shell.swift
いや分かるかい
(おしまい)
Discussion