PythonスクリプトからShebangの意味を考えてみる

こんにちは、iOSエンジニアの @paper_and_paper です。
最近、暑いので引き篭もって統計学を学び直しています。 

## Abstract
スクリプト文の1行目に `#! ...` を書く意味ってご存知でしょうか？

```
#!/bin/sh
```  

最近データ処理を実行するのにPythonスクリプトを書いていると、 
サンプルコードでも `#! /usr/bin/env python3` を何度も目にしています。

とても恥ずかしながら、私は「一応書いておいた方がいいんだろうな〜」という程度。
やはり何度も目にしていると何かと気になります。

- 何で必要なのか？
- 何が実行されているか？
- 書かなくても動作したけど、絶対に必要な1行か？
- シェルやPython以外の言語ではどうなのか？


## スクリプトとは?

そもそも **スクリプト**とは、コンピュータへの命令を寄せ集めしたものです。

私たちがコンピュータに何かを命令し、何かプログラムを実行したいとします。
コンピュータと直接やり取りをすることはできないので、
私たちがやり取りする相手は、 **シェル**などに代表される言語です。
このような言語は人間の代わりにコンピュータとやりとりをしてくれます。
何でそんなことをするかというと、
同じような命令を何回も繰り返したい場合、キーボードをポチポチと何度も叩かないといけない、

そんなの面倒ですよね？

だから、我々はあらかじめ一連の命令をまとめたものをファイルに書くておくわけですね。

そのファイルのことを我々は **スクリプトファイル** と呼んでいます。
（Windowだとバッチファイルと呼ばれる）

## Pythonスクリプトを実行する

「Hello world!」と出力するためスクリプトを作成したとします。

```py:hello.py
#! /usr/bin/env python3

print("Hello, World!")
```

基本的には、スクリプトを実行するには2通りの方法があります。

* 「python ファイル名」で実行する
* 絶対パスまたは相対パスのファイル指定だけで実行する

#### 1. bashコマンドに実行するシェルスクリプトのファイルを指定する。 
これが一番簡単な方法だと思います。
python3コマンドに引数として実行するスクリプトのファイル名を指定します。

```bash:
$ python3 hello.py

Hello, World!
````
ファイル名を指定して実行できるのは、そのファイルがカレントディレクトリにある場合です。
もしファイルが別のディレクトリにある場合は、絶対パスもしくは相対パスで指定します。

#### 2. スクリプトファイルをコマンドとして直接実行する

スクリプトファイルをls, catコマンドのように直接実行するもできます。
この方法で実行するには、主に以下の2つの条件が必要です。

- ファイルのパーミッションに実行権が付与されていること
- pythonがファイルの場所を特定できること

まず、ファイルの実行権限を確認してみましょう。

```
$ ls -l hello.py
-rw-rw-r-- 1 hoge hoge 43  May  7 15:23 hello.py
```

ls -l の結果の一番左から2～4文字目が自分の持っている権限 (正確にはファイル所有者の権限)を表している。rw- だとすると、「読み込み権限: r 」と「書き込み権限: w 」があることになります。
これだと実行権にあたる x がないので、chmodコマンドで権限を付与しておきましょう。

```
$ chmod +x hello.py
```
再度 `ls -l`でしてみて、実行権を意味する x が表示されていれば✌️です。
これで一つ目の条件、ファイルのパーミッションに実行権が付与されていることを満たしたことになります。

次に、python3がファイルの場所を特定できる条件を考えます。

「python3がファイルの場所を特定できる」とは、

- 絶対パス指定でコマンドを実行 = `/home/sunone/hello.py`
- 相対パス指定でコマンドを実行 = `./hello.py`
- PATH の通ったディレクトにファイルを置いてコマンドを実行 = `hello.py`

のいずれかに対応します。

よく使うのはこちら。

```
$ ./helloworld.py

Hello, World!
```
ただし、`helloworld.py`をpython3で実行することを明示的に指定する必要があります。

ここで **シバン** の出番となります。

- [【Python Scriptを実行しよう!】基礎知識まとめ](https://www.sejuku.net/blog/96048)
- [bash シェルスクリプト入門 -シェルスクリプトのいろは-](https://shellscript.sunone.me/tutorial.html)

## シバン（shebang）とは?
何を実現するためにスクリプトを書いたとします。

コンピュータはスクリプトがどの言語で実行すればよいか分かりません。

例えば、実行する前に

「この言語はPythonですよ〜」
「シェルで書かれていますよ〜」

とコンピュータに伝えてあげる必要があるのです。

このため、先頭に指定した #! で始まる「シバン」と呼ばれる文字列を書いておきます。

シバンを書くことで、コンピュータがスクリプトを実行する際のインタプリタを指定できるのです。

- [「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word14689.html)

## シバンを指定しないとどうなるか？

「シバンあり」と「シバンなし」でそれぞれ実行してみました。

```sh:シバンありsample_argv.py
#! /usr/bin/env python3

import sys

inputs = sys.argv

if len(input) > 1:
   for input in inputs:
      print(input)
else:
   print("Hello, World!")
```

```sh:シバンなしsample_argv.py
import sys

inputs = sys.argv

if len(input) > 1:
   for input in inputs:
      print(input)
else:
   print("Hello, World!")
```

シバンを指定せずに `./sample_argv.py` を実行すると以下のようなエラーとなります。
どうやら、スクリプトに書かれたコマンドを解釈できないようです。

```bash:
$ ./sample_argv.py

./sample_argv.py: line 1: import: command not found
./sample_argv.py: line 2: inputs: command not found
./sample_argv.py: line 5: syntax error near unexpected token `inputs'
./sample_argv.py: line 5: `if len(inputs) > 1:'
```

もちろん、当然ながらシバンを書いた方はというと...

```bash:
$ ./sample_argv.py

Hello, World!
```
当然の結果ですね。


**じゃあ、python3コマンドに実行するpyファイルを指定したらどうだろうか？**

やってみました。

```bash:シバンあり
$ python3 sample_argv.py

Hello, World!
```

```bash:シバンなし
$ python3 sample_argv.py

Hello, World!
```

どちらでも正常に実行できました。

つまり、スクリプトファイル自体を **コマンドとして** 直接実行するかどうかがミソなのです。


## PythonスクリプトのShebang(OS別)

Pythonスクリプトのシバンは、OSごとに若干異なります。
(注. Python3で実行する場合)

```py:Window
#! python3

```

```py:Mac
#! /usr/bin/env python3

```

```py:Linux
#! /usr/bin/python3

```
## Python以外だと？

#### Shell

```shell:sh
#!/bin/sh

echo "Remind shebang!!"
```

#### Perl

```per:Perl
#!/usr/bin/perl

print "Remind shebang!!";
```

#### Ruby

```rb:Ruby
#! /usr/bin/env ruby
```

- [Perl, Python 及び Ruby スクリプトにおける正しいshebangの書き方](https://qiita.com/tueda/items/369215b99316b2e9f3d8)
- [【Ruby】Rubyにおける正しいshebangの書き方](https://qiita.com/_ydah/items/05c176970a4ab9d529e8)