#!/bin/sh は ただのコメントじゃないよ! Shebangだよ!


(2017.01.23 追記) 本記事のコメントも併せてご覧ください!

何の話?

  • シェルスクリプトなどでみる#!/bin/shは一体何なのかという話

読むとHappyになれる可能性のある人

  • ファイル冒頭の#!/usr/bin/env pythonというような記述をコメントだと思っている人
  • コメントじゃないことは知っているが何だか分からない人

前置き

hello.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

print('Hello from Python 3.x')
hello.rb
#!/usr/bin/env ruby
puts 'Hello from Ruby'

#!/usr/bin/env pythonとか#!/usr/bin/env rubyという記述をたまに(?)見かけますよね。こやつらは一体何だ? というのがこの記事の話題です。

特に私のようにWeb系のプログラミング言語から入った人はよくわからない人が多いのではないかと思います(私も先日まで知らなかったです)。
「なんかおまじないでしょ?」とか「別に書かなくても動くじゃん?」とか「えっ! 単なるコメントじゃないのこれ!」みたいな人は読んでおくといいかもしれません。

結論

  • #!で始まる行のことをShebangという
  • Shebangにより使用するインタプリタの指定をする
  • Shebangの発音はカタカナで書くなら「シバン」とか「シェバン」とか
  • Unix系に携わるエンジニアなら記述しましょう 実行ファイルとして動かすには記述が必要
  • (副次的な効果として)何のファイルか分かりやすい

書きながら理解しましょう

環境

  • MacOS 10.11.x
  • Ruby 2.3.0(rbenv利用)
  • 環境によってはうまくいかないかもしれません

Rubyファイルをコマンドとして実行する

sample01.rb
#!/usr/bin/env ruby
puts 'Hello from Ruby'

このファイルをターミナル上で素直に実行してみます。

$ ruby sample01.rb
Hello from Ruby

何の変哲もない。単にRubyで記述されたスクリプトを実行しただけです。
次に、このsample01.rbコマンドとして実行してみます。

$ ./sample01.rb
-bash: ./sample01.rb: Permission denied

パーミッションに関していじっていなければこうなる(デフォルトは大体644だと思う)。
では、実行権限を設定してあげましょう。

$ chmod +x sample01.rb

改めてコマンドとして実行します。

$ ./sample01.rb
Hello from Ruby

というわけでコマンドとして実行できたわけです。

#!/usr/bin/env rubyの記述は何なのか

これはインタプリタの指定をしています。

ここで「えっ! インタプリタって何?」となった人はググりましょう。
一応インタプリタをイメージで説明すると、人間が書いたスクリプト(今回はsample01.rb)を機械のために翻訳してくれるやつのことですね。

今回はRubyのインタプリタを指定しているので、うまく解釈してくれたわけです。
では、インタプリタを別の言語のものにするとどうでしょうか?

sample02.rb
#!/usr/bin/env python
puts 'Hello from Python'

今度はPythonのインタプリタを指定しています。
先程と同様にパーミッションを追加してみると

$ chmod +x sample02.rb 
$ ./sample02.rb 
  File "./sample02.rb", line 2
    puts 'Hello from Ruby'
                         ^
SyntaxError: invalid syntax

エラーが返ってきました。これはどういうことでしょうか?

これはこのスクリプトをPythonのインタプリタで解釈したことによるエラーです。記述内容やファイル拡張子はRubyですがインタプリタの指定がPythonになっているためです(Pythonに puts という命令はない)。

それを確かめるためにRubyにはないPythonのメソッドを記述してみましょう。

sample03.rb
#!/usr/bin/env python
print(type('Hello'))
$ ./sample02.rb 
<class 'str'>

インタプリタをRubyに戻すとRubyとしてのエラーが返ってきます。

sample04.rb
#!/usr/bin/env ruby
print(type('Hello'))
$ ./sample02.rb 
./sample02.rb:2:in `<main>': undefined method `type' for main:Object (NoMethodError)

Shebang それは #!で始まる1行目の記述

#!で始まる1行目の記述はShebangと呼びます(「シバン」や「シェバン」と発音するらしい)。

sample04.rb
#!/usr/bin/env ruby
puts 'Shebang'

という実行権限が与えられたスクリプトがあるとします。
この状況で、

$ ./sample04.rb
Shebang

となります。これは今まで見てきたとおりです。

何が起こっているかというと、カーネルが sample04.rb の先頭2バイトが#!なので、それに続く/usr/bin/env rubyを実行するのですが、このときにスクリプトの内容が引数として渡されます

つまり、

$ /usr/bin/env ruby ./sample04.rb

$ ./sample04.rb

は同じ意味になります。

参考: UNIXの部屋 コマンド検索: shebang

うまくいかない場合もある

今回記述した #!/usr/bin/env ruby という記述は多くの環境では動くと思いますが、環境によっては /usr/bin/env が存在しない場合もあるので念のため注意です。

参考: Perl, Python 及び Ruby スクリプトにおける正しいshebangの書き方

Shebangは書くべきなのか?

仕組みがある程度わかったところで実用面の話です。要するにShebangは書くべきなのかということですが、結論「書くべきだ」という方向のようですね。

特に、bashのシェルスクリプトの場合は明示した方が良さそうです。

2017.01.23 訂正 コメントより引用
「Shebang書くべきなのか?」

じゃなくて、書かないと実行ファイルとして動かせませんから。

問題は、
/bin/sh では、シェルが何なのかは判らない
・パスが合っていないかもしれない。 /bin にあったり /usr/bin にあったり、/usr/local/bin にあったり、もしかすると辺境の地にあるかもしれない

前者は、実行させたいシェルを明示することで解決 eg. /bin/bash
後者は、env で解決(することが多い)

参考: シェルスクリプトの罠を避ける三つの tips - Shebang に bash を明示しろ
参考: シェルスクリプトの冒頭でbashを明示する(提案)

おまけ

Shebangの由来

諸説あるようですが、Shebangは Hash(#) と Bang(!) をくっつけた HashBang の省略形らしいです。「!」はBangなんですね。知らなかった。

rbenv使用下でのインタプリタのパス

rbenvを使っていればRubyのインタプリタのパスは /Users/ユーザ名/.rbenv/versions/2.3.0/bin/ruby
になっていると思うのでShebangを次のように書いても動きます。

sample.xxx(拡張子は何でもいい)
#!/Users/ユーザ名/.rbenv/versions/2.3.0/bin/ruby
puts 'Hello'
$ chmod +x sample.xxx
$ ./sample.xxx
Hello
# インタプリタのパスを得る
require 'rbconfig'
Ruby = File::join(RbConfig::CONFIG['bindir'],
                  RbConfig::CONFIG['ruby_install_name'])
Ruby << RbConfig::CONFIG['EXEEXT']

puts Ruby

参考: [ruby]インタプリタのパスを得る

以上