tkuchikiの日記

新ブログ https://blog.tkuchiki.net

現在の shell を shellscript の中から確認する(Linux, BSD 両対応)

現在の shell(以下、current shell) を確認する方法として真っ先に思い浮かぶのは以下だと思います。

$ echo $SHELL
/bin/bash

ただ、これは不正解で、$SHELL はあくまでも login shell ですので、
login shell と current shell が違う場合は正しい値を返してくれません。
私の場合ですと、ローカル環境で常時利用しているのは zsh ですが、
login shell は bash です(zsh を login shell にすると重いと教えていただいたことがあるので)。

このようなケースでも正しい値を返す方法が以下です。

$ echo $0
-zsh

shell 上では $0 に current shell が格納されています。
これで解決すれば良いのですが、shellscript 内で確認しようと思うと話しは変わってきます。
shellscript 内で $0 には、script 名が格納されています。
shell 上での利用よりも、shellscript 内での利用頻度の方が高いでしょうからこれでは困ります。

色々調べていたら、現在使っているシェルの名前を知る方法 - 揮発性のメモ を発見しました。
いろいろ参考になりました、ありがとうございます。

readlink /proc/$$/exe

の説明をすると、
Linux の /proc/PID 以下には、プロセスに関する情報が色々入っていて、

  1. $$ で shellscript 自身の PID を取得して、
  2. シンボリックリンクの実体のパスを返す readlink に、
  3. コマンドのパスのシンボリックリンクが含まれている /proc/$$/exe を渡して、
  4. shellscript を実行している shell を取得する

となります。

これで解決かと思いましたが、以下の欠点があります。

  • /proc が Linux にしかない(たぶん)
  • 実行した時の shell になるので、current shell とは違う

2点目について説明すると、
shebang に #!/bin/bash と指定してスクリプトを実行したり、
bash /path/to/script のように実行すると、zsh を使っていても /bin/bash が返ってくる、ということです。

今回は、あくまでも current shell を取得したいのでこれでは要件を満たせません。

解決策

実行した shellscript の親プロセスは current shell になっているのではないか、
と当たりをつけて実験したらできました。

以下がスクリプトです。
2つ目のスクリプトは、活用例です。

1つずつ説明すると、

_PID=$$

で Shellscript 自身の PID を取得して、

_PPID=$(ps -o ppid -p $_PID | tail -n 1)

で、Shellscript 自身の親PID(PPID)を出力しています。
これを更に分解すると、

$ ps -o ppid -p $_PID
PPID
12345

のような出力になるので、

tail -n 1

で 2 行目だけ出力しているようにしています。
--no-headers を使っていない理由は BSD の ps になかったからです。

あとは、親PIDが取れたので、

ps -p $_PPID

で PID 指定で ps しています。
これで 実行した shellscript の親プロセスの情報を取得出来ました。

zsh を使っている状態でこれを実行すると、

$ ./get_current_shell.sh
  PID TTY          TIME CMD
12345 pts/1    00:00:00 zsh

となります。
header を消したければ、同じ要領で tail をかませればよいでしょう。

あとは、grep -qs zsh にマッチすれば zshgrep -qs bash にマッチすれば bash
といった判定が可能になります。

まとめ

login shell と current shell が違う場合や、
実行時の shell と current shell が違う場合でも、current shell を返す方法を示しました。
それほど使い道はないかもしれませんが、
.bashrc や .zshrc を読み直させたり追記したいときに、
login shell と current shell が違う場合でも、適切なファイルに対して処理が行えるでしょう。
Linux だけでなく BSD でも動くようにしましたので、汎用的に使えるのではないかと思います。

追記1

@hnakamur2 さんに、awk で 2行目をだけを出力するよりも、
tail -n 1 のほうがシンプルだとご指摘いただきましたので修正しました。
ご指摘ありがとうございます!
Linux, BSD で検証して動作することを確認済みです。

# 訂正前
_PPID=$(ps -o ppid -p $_PID | awk 'NR==2 { print }')

# 訂正後
_PPID=$(ps -o ppid -p $_PID | tail -n 1)

追記2

@songmu さんからご指摘頂きました。
ありがとうございます!

shellscript(親shellscript) の中で shellscript(子shellscript) を呼び出す場合は、
子shellscript から見た親プロセスは、親shellscript となってしまいます。
したがって、正しく動作しません。

現状では、まだ解決できていませんが、
current shell 判定処理を関数化して別ファイルにし、

. get_current_shell.sh

get_current_shell

のように読み込んで、子shellscript から呼ばないようにするしかありません。
解決できたら追記致します。