(2017.01.23 追記) 本記事のコメントも併せてご覧ください!
何の話?
- シェルスクリプトなどでみる
#!/bin/sh
は一体何なのかという話
読むとHappyになれる可能性のある人
- ファイル冒頭の
#!/usr/bin/env python
というような記述をコメントだと思っている人 - コメントじゃないことは知っているが何だか分からない人
前置き
#!/usr/bin/env python
# -*- coding: utf-8 -*-
print('Hello from Python 3.x')
#!/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ファイルをコマンドとして実行する
#!/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のインタプリタを指定しているので、うまく解釈してくれたわけです。
では、インタプリタを別の言語のものにするとどうでしょうか?
#!/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のメソッドを記述してみましょう。
#!/usr/bin/env python
print(type('Hello'))
$ ./sample02.rb
<class 'str'>
インタプリタをRubyに戻すとRubyとしてのエラーが返ってきます。
#!/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と呼びます(「シバン」や「シェバン」と発音するらしい)。
#!/usr/bin/env ruby
puts 'Shebang'
という実行権限が与えられたスクリプトがあるとします。
この状況で、
$ ./sample04.rb
Shebang
となります。これは今まで見てきたとおりです。
何が起こっているかというと、カーネルが sample04.rb
の先頭2バイトが#!
なので、それに続く/usr/bin/env ruby
を実行するのですが、このときにスクリプトの内容が引数として渡されます。
つまり、
$ /usr/bin/env ruby ./sample04.rb
と
$ ./sample04.rb
は同じ意味になります。
うまくいかない場合もある
今回記述した #!/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を次のように書いても動きます。
#!/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
以上