シバン(shebang)にシェル以外のコマンドを指定してシバンの動作を確かめてみる

### 今回の動作確認環境
- Vagrant 2.2.14 
- CentOS 8
- bash 4.4.19

### shebangで指定可能な形式について

[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix))とは実行可能権限が付与されたシェルスクリプトの先頭行に書かれている `#!/bin/sh` の部分のことを指します。

シェルスクリプトでよく見かける形式としては、以下のようなものではないでしょうか。

```
#!/bin/sh
#!/usr/bin/bash
```

スクリプト言語を利用する方であれば、こういった形式で書かれているかと思います。

```
#!/usr/bin/perl
#!/usr/local/bin/ruby
#!/usr/bin/env python3
```

`/usr/bin/perl` や `/usr/bin/env` からわかるように、 `/etc/shells` に記載されているようなシェルのパス以外の実行ファイルでも指定できることに気づきます。

[Wikipedia](https://en.wikipedia.org/wiki/Shebang_(Unix))によると `shebang` として指定可能なのは「the file specified by interpreter must be an executable binary and cannot itself be a script.」とのこと。
つまり、実行可能なバイナリファイルで、shebangを書いてるシェルスクリプト自身以外であればOKみたいです。

Wikipedia内に[execveのマニュアルページへの参照リンク](https://linux.die.net/man/2/execve)が貼られていたので  `man execve` を見てみると以下のように `DESCRIPTION` に記述されていました。

```
DESCRIPTION
       execve()  executes  the  program pointed to by filename.  filename must be either a
       binary executable, or a script starting with a line of the form:

           #! interpreter [optional-arg]
```

`shebang` に指定できるのは、以下のいずれかのようです。

1. 実行可能なバイナリ
1. 「#!」で始まるシェルスクリプト

「1」については「/usr/bin/bash」、「/usr/bin/perl」、「/usr/bin/env」といった記述から理解できます。
「2」については、ちょっと試してみましょう。

vagrantユーザのホームディレクトリ直下に `like_echo.sh` という第1引数をechoするシェルスクリプトを準備して、実行権限を付与します。

```
$ pwd
/home/vagrant

$ cat like_echo.sh 
#!/bin/bash
echo $1

$ chmod +x like_echo.sh 
```

`like_echo.sh` に `arg1` を渡して呼び出す `call_like_echo.sh` を準備して、これにも実行権限を付与します。

```
$ cat call_like_echo.sh 
#!/home/vagrant/like_echo.sh arg1

$ chmod +x call_like_echo.sh 
```

実行してみると `arg1` が出力されました。これで「or a script starting with a line of the form:〜」の部分の検証ができたかと思います。

```
$ ./call_like_echo.sh
arg1
```

ちなみ `call_like_echo.sh` で`shebang` 行で渡す引数の数を増やすとこんな動きになります。

引数を2つ渡して実行。

```
$ cat call_like_echo.sh 
#!/home/vagrant/like_echo.sh arg1 arg2

$ ./call_like_echo.sh
arg1 arg2
```

引数を3つ渡して実行。

```
$ cat call_like_echo.sh 
#!/home/vagrant/like_echo.sh arg1 arg2 arg3

$ ./call_like_echo.sh
arg1 arg2 arg3
```

この動きから `like_echo.sh` で受け取った `$1` には `call_like_echo.sh` の `shebang` 行で指定した「arg1 arg2 arg3」の部分すべてが第1引数として扱われているようです。

### echoで実行状況を可視化

ここまでで `shebang` には `/bin/sh` のようなシェル以外でも `man execve` に記載されている条件を満たしていれば指定できることがわかりました。

また、さきほどの `like_echo.sh` の動作から、 `shebang` で `#!/bin/bash` と指定すると、以下のような呼び出され方をしていることがわかります。

```
$ /bin/bash like_echo.sh 引数
```
当然と言えば当然の動きなんですが。。。この動きを `echo` コマンドを使って可視化してみましょう。

`shebang` に `echo` コマンドのパスを記述して、実行権限を付与します。

```
$ cat echo.sh 
#!/usr/bin/echo

$ chmod +x echo.sh 
```

実行すると、シェルスクリプト自体のファイル名が出力されました。

```
$ ./echo.sh 
./echo.sh
```

ということは `shebang` 行に `#!/usr/bin/echo` と指定することで、実際にはこのように呼び出されたことがわかります。

```
$ /usr/bin/echo ./echo.sh
```

引数を渡してみると `/usr/bin/echo ./echo.sh 引数` のように呼び出されていることが可視化できていますね。

引数が1つの場合。

```
$ ./echo.sh arg1
./echo.sh arg1
```

引数が2つの場合。

```
$ ./echo.sh arg1 arg2
./echo.sh arg1 arg2
```

### wcで行数を数えてみる

`shebangで指定したコマンド ./自身のファイル名 引数` の形式でシェルスクリプトが実行されることがわかったので、この仕組みを使ってファイルの行数を数えてみます。

こんなファイルを準備して、実行権限を付与します。

```
$ cat wc.sh 
#!/usr/bin/wc -l
# Comment1
# Comment2
# Comment3
# Comment4
# Comment5

$ chmod +x wc.sh
```

実行するとファイルの行数が出力されました。

```
$ ./wc.sh 
6 ./wc.sh
```

### cpでシェルスクリプトのコピーを作成する

こうなってくると、自身のファイル名を使った操作が色々と可能ですね。
ファイルのコピーを行うスクリプトを準備して、実行権限を付与します。

```
$ cat cp.sh 
#!/usr/bin/cp

$ chmod +x cp.sh
```

引数に `cp.sh.YYYYMMDDHHMMSS` 形式のファイル名を渡すと `cp.sh` がコピーされて `cp.sh.20210711042853` が作成されました。

```
$ ./cp.sh cp.sh.`date '+%Y%m%d%H%M%S'`
$ ls -l cp.sh.20210711042853 
-rwxrwxr-x. 1 vagrant vagrant 14 Jul 11 04:28 cp.sh.20210711042853
```

### まとめ

1. `shebang` は「実行可能なバイナリ」もしくは「`#!`で始まるシェルスクリプト」が指定できる。
1. `shebang` に `#!/PATH/TO/コマンド コマンドの引数` と記述して `./シェルスクリプト名 シェルスクリプトの引数` のように実行すると、実際には `/PATH/TO/コマンド コマンドの引数 ./シェルスクリプト名 シェルスクリプトの引数` のような形式で実行されている。

### 参考URL
- [シバン（shebang）にスクリプトを実行しないコマンドを指定してみる実験](https://qiita.com/HMMNRST/items/7a9f927014ad2fb3befc)
- [シェルスクリプトのシバン(Shebang)にシェル以外のコマンドを記述したらどうなるのか？](https://genzouw.com/entry/2020/09/27/231631/2073/)
- [作業の自動化、謎のおまじない shebang（シバン）、PATH を設定する](https://fjord.jp/kuroigamen/8.html)
- [What is shebang (#!/bin/sh) in POSIX shell script](https://senooken.jp/post/2017/01/01/)
- [シェルのshebang が実行されるときの引数を見てみる。](https://takuya-1st.hatenablog.jp/entry/2018/08/01/171752)
- [shebangから理解するUNIXの仕組み](https://chaingng.github.io/post/shebang_init/)
- [/bin/shと/bin/bashの違い(とcronでのシェル)](https://rcmdnk.com/blog/2016/09/20/computer-linux-bash/)
- [スクリプト言語 C —— shebangのないテキストファイルはいかに実行されるか](https://qiita.com/8x9/items/4f1c2395e9aed9b4c32b)
- [PythonスクリプトからShebangの意味を考えてみる](https://qiita.com/Nick_paper/items/b9655e02721a583f29b5)
- [Perl, Python 及び Ruby スクリプトにおける正しいshebangの書き方](https://qiita.com/tueda/items/369215b99316b2e9f3d8)
- [シェルスクリプトの冒頭でbashを明示する（提案）](https://qiita.com/jkr_2255/items/84366f677be3365331cd)
- [#!/bin/sh は ただのコメントじゃないよ！ Shebangだよ！](https://qiita.com/mohira/items/566ca75d704072bcb26f)
- [bash スクリプトの先頭によく書く記述のおさらい](https://moneyforward.com/engineers_blog/2015/05/21/bash-script-tips/)
- [手を動かして理解するshebang](https://qiita.com/takey/items/ae2124531fb57a197b22)
- [シバン (shebang) を書いた方が可搬性が高いシェルスクリプトになる](https://qiita.com/ko1nksm/items/c244752c7f3d0df5e723)
- [shebang](https://qiita.com/bsdhack/items/40680c50d2ed13dcb02b)