シェルスクリプトの中で現在利用しているシェルを確認する方法

みなさん、シェル使ってますか？私は毎日使ってます。
「シェルとは？」といった記事はたくさんあるのでここでは深く触れませんが、ざっくり言うと「コマンドでカーネルと対話的なインターフェースを提供する仕組み」といった感じでしょうか。
そんなシェルについてですが、「シェルスクリプトの中で現在利用しているシェルを確認する方法」が知りたくてちょっと調べてみました。

---

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

※Dockerを利用すると `$SHELL` （ログインシェル（デフォルトシェル））の表示の際に期待した動作とは異なりました。Dockerの場合は動作に必要な最小構成になっていることがあったり、ホストPC上で動作している仕組みのためにこのようなことが起こるのかもしれません。そういったことを踏まえて今回はVagrantを使ってみてます。（このあたりは詳しく調べきれてないところではあります）

---

[「LinuCレベル1」の試験範囲](https://linuc.org/linuc1/range/)に「GNUとUnixのコマンド」という章があり、試験勉強をしているとその中で以下のコマンドで「現在のシェル」を知ることができると説明されています。

```
$ echo $SHELL
/bin/bash
```

完。。。

と思うじゃないですか。
でも、この `$SHELL` は想定とは違う動きをします。

↑のコマンドはCentOS8の環境で実行したんですが、CentOS8の初期状態は利用できるシェルが少ない状態です。
以下のコマンドで確認すると `sh` と `bash` のみです。

```
$ cat /etc/shells 
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
```

しかも `sh` は `bash` へのシンボリックリンクになっているので実質1種類と言えます。

```
$ ls -l /usr/bin/sh
lrwxrwxrwx. 1 root root 4 May 11  2019 /usr/bin/sh -> bash
$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 May 11  2019 /bin/sh -> bash
```

なので、  `zsh` 、 `ksh` 、 `tcsh` をインストールします。

```
$ sudo yum install zsh ksh tcsh -y
```

このように利用可能なシェルが増えました。

```
$ cat /etc/shells 
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
/usr/bin/zsh
/bin/zsh
/bin/csh
/bin/tcsh
/usr/bin/csh
/usr/bin/tcsh
/bin/ksh
/bin/rksh
/usr/bin/ksh
/usr/bin/rksh
```

それでは改めて `$SHELL` を見てみましょう。

`zsh` に変更して。。。おや？

```
$ zsh
% echo $SHELL
/bin/bash
```

`ksh` に変更して。。。おやおや？

```
$ ksh
$ echo $SHELL
/bin/bash
```

`tcsh` に変更して。。。おやおやおや？

```
$ tcsh
$ echo $SHELL
/bin/bash
```

期待している動作と異なりますね。
`$SHELL` は現在のシェルではなく、どうやらログインシェル（デフォルトシェル）を表示しているようです。
こういった場合に使えるのが `$0` です。

ログイン直後でデフォルトシェルが `bash` の場合は。

```
$ echo $0
-bash
```

`zsh` に変更すると。

```
$ zsh
% echo $0
zsh

```

`ksh` に変更すると。

```
$ ksh
$ echo $0 
ksh
```

`tcsh` に変更すると。

```
$ tcsh 
$ echo $0
tcsh
```

完。。。といった感じですね。

---

それではタイトルのとおり、「シェルスクリプトの中で現在利用しているシェル」を確認していこうと思います。

まずは `$0` を表示するスクリプトを記述したファイルを作成します。

```
$ echo 'echo $0' > show_shell.sh
```

それでは `bash` で実行してみます。

```
$ bash show_shell.sh 
show_shell.sh
```

ファイル名が表示されました。
そりゃそうです。 `man bash` を開くと `ARGUMENTS` のところに `$0 is set to the name of the file` と明記されています。
ついこの前書いた「[bashの特殊変数の説明を読んでもよくわからなかったので試してみた](https://qiita.com/akase244/items/89242daed53363d9486f)」という記事の中でも、 `$0` は「スクリプトファイル名」と説明していますし。。。

困りましたね。どのシェルで呼び出してもすべて同じ結果です。

```
$ zsh show_shell.sh 
show_shell.sh
```

```
$ ksh show_shell.sh 
show_shell.sh
```

```
$ tcsh show_shell.sh 
show_shell.sh
```

---

そこで登場するのが `/proc` ディレクトリです。
`/proc` 配下のファイルには色々な情報が詰まっている特別なディレクトリで、 `/proc/cpuinfo` や `/proc/meminfo` なんかは利用したことがあるんじゃないでしょうか。
`/proc` 配下のファイルは通常のファイルとは異なる形式になっていて、ファイルサイズが0だったり、作成日時が現在時刻だったりと不思議な作りになっています。

```
$ ls -l /proc/cpuinfo
-r--r--r--. 1 root root 0 Jul  9 16:56 /proc/cpuinfo
$ ls -l /proc/meminfo
-r--r--r--. 1 root root 0 Jul  9 16:56 /proc/meminfo
```

`/proc` 配下のファイルを見てみると、数字のディレクトリが存在しています。これはプロセスIDと一致しています。

```
$ ls /proc/
1    1902   25   441  636  725        execdomains  loadavg       softirqs
10   2      26   442  637  8          fb           locks         stat
11   20     27   443  656  9          filesystems  mdstat        swaps
12   21     28   444  659  acpi       fs           meminfo       sys
13   22     29   445  661  asound     interrupts   misc          sysrq-trigger
14   23     3    446  662  buddyinfo  iomem        modules       sysvipc
141  24     30   447  663  bus        ioports      mounts        thread-self
142  24687  31   448  666  cgroups    irq          mtrr          timer_list
143  24697  32   48   667  cmdline    kallsyms     net           tty
144  24701  4    535  689  consoles   kcore        pagetypeinfo  uptime
146  24705  416  561  691  cpuinfo    key-users    partitions    version
147  24711  418  6    692  crypto     keys         sched_debug   vmallocinfo
16   24712  420  619  699  devices    kmsg         schedstat     vmstat
17   24844  421  624  711  diskstats  kpagecgroup  scsi          zoneinfo
18   24874  422  625  715  dma        kpagecount   self
19   24882  440  635  724  driver     kpageflags   slabinfo
```

「[bashの特殊変数の説明を読んでもよくわからなかったので試してみた](https://qiita.com/akase244/items/89242daed53363d9486f)」の中で「現在実行しているシェルのPID」は `$$` で表現されると説明しました。
これを応用して、 `ls /proc/$$` を実行してみましょう。すると現在のプロセスIDで動いている固有の情報が詰まったファイルの一覧が表示されます。

```
$ ls /proc/$$
attr                cpuset   loginuid    numa_maps      sched         status
autogroup           cwd      map_files   oom_adj        schedstat     syscall
auxv                environ  maps        oom_score      sessionid     task
cgroup              exe      mem         oom_score_adj  setgroups     timens_offsets
clear_refs          fd       mountinfo   pagemap        smaps         timers
cmdline             fdinfo   mounts      patch_state    smaps_rollup  timerslack_ns
comm                gid_map  mountstats  personality    stack         uid_map
coredump_filter     io       net         projid_map     stat          wchan
cpu_resctrl_groups  limits   ns          root           statm
```

この中で注目すべきファイルは `exe` です。
`man proc` を見てみると、 `/proc/[pid]/exe` は `this file is a symbolic link containing  the actual pathname of the executed command.` のように説明されています。

「実行されたコマンドの実際のパス名を含んだファイル」ということは、 `/proc/$$/exe` を `ls` してみると。。。現在実行しているシェルへのシンボリックリンクが表示されました！

```
$ ls -l /proc/$$/exe
lrwxrwxrwx. 1 vagrant vagrant 0 Jul  9 17:13 /proc/24712/exe -> /usr/bin/bash
```

シンボリックリンクのリンク先の名前を表示する `readlink` というコマンドを組み合わせると。。。来ました！これが求めていたものです！！

```
$ readlink /proc/$$/exe
/usr/bin/bash
```

事前に準備していた `show_shell.sh` を書き換えます。

```
$ echo 'readlink /proc/$$/exe' > show_shell.sh
```

それぞれのシェルで実行してみると、「シェルスクリプトの中で現在利用しているシェル」を表示することができました。

```
$ bash show_shell.sh
/usr/bin/bash
```

```
$ ksh show_shell.sh
/usr/bin/ksh93
```

```
$ zsh show_shell.sh
/usr/bin/zsh
```

```
$ tcsh show_shell.sh
/usr/bin/tcsh
```

この `/proc/$$/exe` は `shebang` にも有効です。
実行可能なファイルの場合、シェルスクリプトは `shebang` を元にどのシェルを利用するのかを決定するので、 `show_shell.sh` 次のように書き換えて試してみます。

実行権限を付与しておいて。

```
$ chmod +x show_shell.sh
```

まずは `bash` の場合。このように `shebang` を書いて。

```
$ cat show_shell.sh
#!/usr/bin/bash
readlink /proc/$$/exe
```

実行。

```
$ ./show_shell.sh 
/usr/bin/bash
```

`zsh` の場合も同様に  `shebang` を書き換えて。

```
$ cat show_shell.sh
#!/usr/bin/zsh
readlink /proc/$$/exe
```

実行。

```
$ ./show_shell.sh 
/usr/bin/zsh
```

---

ここで、さらに実験です。実行可能なファイルで `shebang` が書かれていない場合はどうなるのでしょうか。

```
$ cat show_shell.sh
readlink /proc/$$/exe
```

CentOS8の場合は `/usr/bin/bash` が選択されました。

```
$ ./show_shell.sh 
/usr/bin/bash
```

`shebang` が無い場合の実行可能ファイルのシェルの決定ロジックについては複雑なようで、「実行可能ファイルの場合はどのシェルが利用されるかわからないような曖昧な状態にせず `shebang` を書いて意図どおりのシェルが選択されるようにしましょう」ということみたいですね。

### 参考URL
- [proc(5) — Linux manual page](https://man7.org/linux/man-pages/man5/proc.5.html)
- [/proc/(数字)ディレクトリ](https://linuc.org/study/knowledge/530/)
- [/proc/\<pid\>/ディレクトリに存在するファイル](https://linuc.org/study/knowledge/532/)
- [Bash script execution with and without shebang in Linux and BSD](https://stackoverflow.com/questions/7268437/bash-script-execution-with-and-without-shebang-in-linux-and-bsd)
- [Shell script working fine without shebang line? Why? [duplicate]](https://stackoverflow.com/questions/12296308/shell-script-working-fine-without-shebang-line-why/12296783)
- [使っているシェルを調べる方法](https://news.mynavi.jp/article/20181121-727279/)
- [現在の shell を shellscript の中から確認する(Linux, BSD 両対応)](https://tkuchiki.hatenablog.com/entry/2014/05/08/222135)
- [現在使っているシェルの名前を知る方法](https://iww.hateblo.jp/entry/20131108/dash)
- [現在実行中のシェルを知る方法](https://qiita.com/bloody_snow/items/48b6afda448038f5b826)
- [現在利用しているシェルの種類を確認する(GNU BashなのかZ Shellなのか)](https://qiita.com/UmedaTakefumi/items/fbb6505c4eb225cd63c5)
- [現在使用しているシェルを確認](https://qiita.com/hm0429/items/4f722588db133d3231a6)