nakamurk’s diary

日々思うことは残していきます。しっかり生きます。

shebangについて考える

Bashが好きです。スクリプトのTry&Errorで作っていく感じが性に合っているように思います。大学院時代からちょこちょこと触り始めたので、2017年現在で6年目となりますが、Bashは奥が深く。。という言葉で逃げてしまうと永遠にその背中に追いつけないような気がするので、一つ一つ理解を深めていいけたらと思い、記事にまとめてみます。こちらの記事はパッチワーク的に書いていこうと思います。

shebangって何?

シェバン手始めにwikiで調べてみましょう*1。日本語のwikiよりも英語のwikiの方がより詳しく書かれていますね。と言うか、なんだか書くことがなくなった感がありますね笑 まあ、気を取り直してgoogle翻訳を駆使して理解していきましょう。

Unixライクなシステムにおいて、shebangで始まるプログラムとして実行されたとき、 program loader*2shebanginterpreter directive*3として解析していきます。

 訳が上手くないとは言え、いきなり二つも人に説明できない事柄にぶつかってしまいました。。。これまでの私は「Bashなどのシェルやcronでプログラムを実行した際に、shebangが指定されているとshebangよりも後のプログラムがshebangで指定されたプログラムとして解釈される」と理解していました。合っているような、間違っているような、微妙ですね(ーー;)

例えばこんな風に書いたらどうなるのだろうか

例えばshebangを書かないで実行権限だけつけたBashスクリプトBashで実行するとどうなるかと言えば、次のようになります。

    Bash# cat shebang_1.sh
    echo shebang
    Bash# chmod 755 shebang1.sh
    Bash# ./shebang_1.sh
    shebang

見たまんまですね。スクリプトBashが解釈するので「shebang」とechoされています。 次に同じようにshebangを書かないパターンとして、Rubyでやってみたらどうでしょうか

    Bash# cat shebang_2.rb
    puts "shebang"
    Bash# chmod 755 shebang_2.rb
    Bash# ./shebang_2.rb
    ./shebang_2.rb: line 1: puts: command not found
    Bash# ruby ./shebang_2.rb
    shebang

こちらについても、予想通りの出力になったかと思います。putsコマンドが見つかりません。とのことですね。こちらはPathが通っていないコマンドを実行してしまった場合に見かけるエラーですね。rubyをコマンドとして指定すれば問題なく実行できるようです。では、Bashから直接Rubyスクリプトを実行するにはどのようにすればよいのでしょうか?そうです、shebangの出番ですね!

    Bash# cat shebang_3.rb
    #!/usr/bin/ruby
    puts "shebang"
    Bash# chmod 755 shebang_3.rb
    Bash# ./shebang_3.rb
    shebang

でもここでいちいちfull-pathを指定するのが面倒になるわけですね。「えいや」でrubyだけ指定したらどうなるか試してみましょうw

    Bash# cat shebang_4.rb
    #!ruby
    puts "shebang"
    Bash# chmod 755 shebang_4.rb
    Bash# ./shebang_4.rb
    -bash: ./shebang_4.rb: ruby: bad interpreter: No such file or directory

当然ダメですね。さてここでinterpreterという単語が出てきましたね、日本語にすると「通訳」となるようですね。「Rubyなんて知らないよ」ということですね。だからfull-pathで指定しなきゃいけないと。。。面倒ですね。しかも、インストール先が異なるシステム間ではスクリプトの融通が利かなくなってしまいます。そこで登場するのが「env」コマンドですね。上のスクリプトは、次のように書けばよかったわけです。

    Bash# cat shebang_5.rb
    #!/bin/env ruby
    puts "shebang"
    Bash# chmod 755 shebang_5.rb
    Bash# ./shebang_5.rb
    shebang

いい感じです。このように書けば異なるシステム間でも、スクリプトを共有できるわけです。そして、ここで落とし穴が待ち受けているわけです。そうですcronです。こちらのRubyスクリプトをcronで起動してみましょう。

    Bash# crontab -ls
    */1 * * * * /root/work/shebang_5.rb
    You have mail in /var/spool/mail/root
    ...
    shebang
    ...

前振りも空しく、普通に実行できてしまいましたね。環境変数を確認してみましょう。

    Bash# crontab -ls
    */1 * * * * env
    You have mail in /var/spool/mail/root
    ...
    PATH=/usr/bin:/bin
    ...

そりゃ問題なく実行できますよね。正しくPathが通っていますもの。。。職場でこの問題にはまったのですが、よく考えてみたら自分たちでインストールしたRubyのPathが、変なところにあってenvコマンドが使えないだけでした。。。 とりとめもなく書きすぎてしまいましたがいつの日か清書します。

いつかやる宿題
  • 「program loader」はどうやって動いているのか
  • Interpreter directive」とはどういう意味か

*1:Shebang (Unix) - Wikipedia

*2:ローダーのことは「Bashスゲー便利」で、これまで鵜呑みにしてきました。根本から勉強することは今の私の知識では不十分です。当分こちらについてペンディングします。

*3:こちらについては日本語訳がさっぱりわかりません。。。勉強不足です