環境
Raspbian 8.0
shebangとは何か
shebangはシバンまたはシェバンと読みます。
Unix系のソースファイルの一行目に書いてある、#!/usr/bin/env python
みたいなやつです。
shebangとは、実行時にインタプリタを指定するものです。
その概要については、以下の記事が詳しいです。
#!/bin/sh は ただのコメントじゃないよ! Shebangだよ!
ここではshebangの挙動をPythonを使って確認してみます。
まず自分のPythonの環境を確認します。
$ python -V
Python 2.7.9
$ python3 -V
Python 3.4.2
私の環境では、pythonを指定した場合は2.x系、python3を指定した場合は3.x系でした。
次にshebangの挙動を確認するためのテストコード"test_shebang.py"を書きます。
#!/usr/bin/env python
print "this is test_shebang."
上記のテストコードは、2.x系では動作し、3.x系ではエラーが起きる簡単なコードです。
その事を確認するため、2.x系と3.x系でそれぞれ動かしてみます。
$ python test_shebang.py
this is test_shebang.
$ python3 test_shebang.py
File "test_shebang.py", line 3
print "this is test_shebang."
^
SyntaxError: Missing parentheses in call to 'print'
では次に、"test_shebang.py"をコマンドとして実行してみます。つまりpythonコマンドを使用せずに実行させます。適切な実行権限がない場合「権限がありません」と言われるので、その場合は実行権限を与えてください。
$ chmod 777 test_shebang.py # 適当に実行権限を与える
$ ./test_shebang.py
this is test_shebang.
エラーなく動作しました。つまり2.x系で動作したということです。
今度は"test_shebang.py"を3.x系で動作するようにshebangを書き換えてみます。
#!/usr/bin/env python3
print "this is test_shebang."
書き換えました。コマンドとして実行してみます。
$ ./test_shebang.py
File "./test_shebang.py", line 3
print "this is test_shebang."
^
SyntaxError: Missing parentheses in call to 'print'
エラーが発生しました。つまり3.x系で動作したということです。
このように、shebangで実行時のインタプリタを指定することができます。
ところで、#!python
だけで動きそうですが、実は動きません。
#!python
print "this is test_shebang."
実行してみます。
$ ./test_shebang.py
bash: ./test_shebang.py: python: 誤ったインタプリタです: そのようなファイルやディレクトリはありません
実はshebangは絶対パス(フルパス)で指定しなければなりません。
$ which python # 絶対パスを調べる
/root/.pyenv/shims/python
"test_shebang.py"のshenabgを絶対パスで指定してみます。
#!/root/.pyenv/shims/python
print "this is test_shebang."
実行してみます。
$ ./test_shebang.py
this is test_shebang.
なるほど、これでshebangの挙動が理解できました。
envって何
/usr/bin/env
とは何か。
Wikipediaではこう書いてあります。
インタプリタの代わりにenvを指定することで、スクリプトの実行時にPATHからインタプリタが検索され実行される。これにより同じスクリプトが、より多くの環境で動く可能性が高くなる。
つまり絶対パスで書くと人によってはパスが異なる場合があるけど、envを使えば自動でPATHからインタプリタを検索して実行してくれるので、移植性が高くなるということです。
shebangの使い所
#!/usr/bin/env sh この表記のシェバングは本当に必要なの?
shebangを使わなくても、pythonまたはpython3を指定して実行させればいいのでは、という疑問があります。
shebangはどういった所で使うのでしょうか。あるいはコマンドとして実行したいときはどういったときか。
ここではsystemdのserviceを使って必要性を確認してみます。
systemdは「ユニット」と呼ばれるいわゆるデーモンを作成・削除・操作するコマンドです。これにより常駐プログラムなどを作成することができます。
たとえばデーモン(service)を作成するときは以下のようなファイルを作成します("hogehoge.service"ファイルの作成)。
$ vi /etc/systemd/system/hogehoge.service
[Unit]
Description = hogehoge.service runs.
[Service]
ExecStart = /home/pi/sandbox/hogehoge.py
Restart = no
Type = simple
[Install]
WantedBy = multi-user.target
詳しい説明は省きますが、見てほしいところはExecStartの行で、ここに動かしたいプログラムファイルを指定します。
上記ではhogehoge.pyを指定しています。
hogehoge.pyを作成します。
#!/usr/bin/env python3
# coding: utf-8
from datetime import datetime
if __name__ == "__main__":
f = open("/var/log/hogehoge.log", "w")
f.write(str(datetime.now()))
f.close()
これは、プログラムを実行する度に/var/log/hogehoge.logに現在時刻が書き込まれる単純なプログラムです。
shebangでpython3を指定しています。
これを先程のsystemdを使ってhogehoge.serviceを実行してみます。
$ chmod 777 hogehoge.py # 実行権限を与えておく
$ systemctl start hogehoge.service
正しく実行できたら、hogehoge.logに現在時刻が書き込まれています。
$ less /var/log/hogehoge.log
2018-08-02 20:24:53.765429
書き込めています。うまく動作しています。
話をshebangの必要性に戻します。ここでhogehoge.pyのshebangをなくすとどうなるでしょうか。
# coding: utf-8
from datetime import datetime
if __name__ == "__main__":
f = open("/var/log/hogehoge.log", "w")
f.write(str(datetime.now()))
f.close()
shebangの行を削除しました。この状態でhogehoge.serviceを実行してみますと、実行に失敗します。
$ systemctl start hogehoge.service
$ systemctl status hogehoge.service -l
● hogehoge.service - hogehoge.service runs.
Loaded: loaded (/etc/systemd/system/hogehoge.service; disabled)
Active: failed (Result: exit-code) since 金 2018-08-02 20:48:36 JST; 3s ago
Process: 19370 ExecStart=/home/pi/sandbox/hogehoge.py (code=exited, status=203/EXEC)
Main PID: 19370 (code=exited, status=203/EXEC)
8月 02 20:48:36 raspberrypi systemd[1]: Started hogehoge.service runs..
8月 02 20:48:36 raspberrypi systemd[1]: hogehoge.service: main process exited,
code=exited, status=203/EXEC
8月 02 20:48:36 raspberrypi systemd[1]: Unit hogehoge.service entered failed state.
root@raspberrypi:/hoge/pi/sandbox#
実行失敗の原因は、shebangがないことでコマンド実行ができなかったからです。当然といったら当然です。
ではこのshebangがない状態でserviceを実行させるにはどうすればいいか。考えられる方法としてはhogehoge.serviceでpython3を指定して実行させる方法があります。
ただし、ExecStartには絶対パスで指定する必要があります(単にpython3と書いたらエラーが起きます)
$ which python3 # 絶対パスを調べるコマンド
/root/.pyenv/shims/python3
$ vi /etc/systemd/system/hogehoge.service
[Unit]
Description = hogehoge.service runs.
[Service]
ExecStart = /root/.pyenv/shims/python3 /home/pi/sandbox/hogehoge.py
Restart = no
Type = simple
[Install]
WantedBy = multi-user.target
または、
ExecStart = /usr/bin/env python3 /home/pi/sandbox/hogehoge.py
なら動作します。
また他の方法としては、
ExecStart = /home/pi/sandbox/start_hogehoge.sh
とし、start_hogehoge.shを、
#!/bin/sh
python3 /home/pi/sandbox/hogehoge.py
とすることで、シェルスクリプトを介することで.pyにshebangを書かなくて良い方法があります。
しかしこの方法はシェルスクリプトを介するのでプログラム実行の構造が冗長になってしまう欠点と、「結局シェルスクリプトにshebang書いているじゃないか」という突っ込みと、「どうしてそこまで.pyにshebangを書きたくないんだ」という突っ込みがあります。
しかしシェルスクリプトならではの、コマンドを自由に列挙できるメリットもありますので一概に悪いとまでは言い切れません。ケースバイケースでしょう。
#!/bin/sh
python3 /home/pi/sandbox/hogehoge.py
python2 /home/pi/sandbox/piyopiyo.py
python /home/pi/sandbox/fugafuga.py
補足
shebangは、2個以上のスペースで区切った場合は意図しない処理になる場合があります。
参考URL:本の虫, Shebangという謎な事実上業界標準について
あるOSは、#!から最初の空白文字までの文字列をインタープリターへのパスとみなし、最初の空白文字から改行までの文字列を、インタプリターへのひとつの引数として渡す。
#! インタプリターへのパス 引数 [改行]
つまり、以下のようなshebangの場合、
#! /usr/bin/interpreter -a -b -c
引数は"-a -b -c"ひとつとなる。
Raspbianでも同じ現象が起きますので、それを確認してみます。
まず次のようなtest_shebang2.pyを作成します
assert 1 == 2
print "this is test_shebang2."
assert文は条件式がFalseのとき例外を発生させます。-Oオプションを付けて実行することでassert文を無視して実行することができます。
これをオプション無しで実行してみます。
$ python test_shebang2.py
Traceback (most recent call last):
File "./test_shebang2.py", line 3, in <module>
assert 1 == 2
AssertionError
-Oオプションを付けて実行することでassert文を無視することができます(例外が発生しません)。
$ python -O test_shebang2.py
this is test_shebang2.
それでは今度はshebangで実行してみます。
test_shebang2.pyを次のように書き換えます。
#!/usr/bin/env python -O
assert 1 == 2
print "this is test_shebang."
これをコマンド実行してみます。
$ ./test_shebang2.py
/usr/bin/env: python -O: そのようなファイルやディレクトリはありません
"python -O"がひとまとめの引数となってしまってエラーが発生しています。
これを根本的に回避する方法はないようで、絶対パスで指定して空白行を減らす工夫くらいしかなさそうです。
#!/root/.pyenv/shims/python -O
assert 1 == 2
print "this is test_shebang."