要約
いま動いているインタラクティブシェルを知る方法に環境変数$SHELL
を読むのが広く知られているが、これはこの環境変数の誤った使い方である。
$SHELL
はユーザーのお気に入りのシェルを指定するものである($EDITOR
に近い)。
シェルは起動時に$SHELL
の値をいまのシェルにセットするわけではない。
いま動いているシェルを知るには
ps -o comm= -p $$
とするのが良さそうだ。
Googleで「現在のシェル 確認」と検索すると
echo $SHELL
でわかると書いてあるページが多い。たまにecho $SHELL
ではなくecho $0
とやれと書いてある記事もある。
POSIXの説明
SHELL
This variable shall represent a pathname of the user's preferred command language interpreter. If this interpreter does not conform to the Shell Command Language in XCU Shell Command Language, utilities may behave differently from those described in POSIX.1-2017.
この説明からわかるように$SHELL
は現在の動いているシェルを表すものでは無い。
試してみる
簡単な実験で$SHELL
が現在走っているシェルを示さないことがわかる。
# いまは zsh です。 % uname -a Darwin Ryuichis-MacBook-Pro.local 22.4.0 Darwin Kernel Version 22.4.0: Mon Mar 6 20:59:58 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T6020 arm64 % echo $SHELL /bin/zsh % % bash # bashを起動するも、 $ echo $SHELL # $SHELLの値は変わらない。 /bin/zsh $ $ ksh $ echo $SHELL /bin/zsh
このようにbashやkshは環境変数$SHELL
を/bin/bash
に上書きしてくれるわけでは無い。
このようにいま動いているシェルと$SHELL
が異なる状態で$SHELL
からいま動いているシェルを知ろうとする(行儀の悪い)スクリプトをsource
や.
で読むとおかしい挙動をしたりする。
だったらどうやっていまのシェルを調べればよいのだろう。
$0
は罠がある
$0
はC言語でのargv[0]
を示すので、インタラクティブシェルでは$0
は現在のシェルのパスを表す(シェルスクリプトでは起動したファイルの名前を表す)。
# bash $ echo $0 bash # ksh $ echo $0 ksh # sh $ echo $0 sh # dash $ echo $0 dash # zsh % echo $0 -zsh
(最後のzshのようにログインシェルはprefixに-
がついている。)
「これでいまのシェルを知ればいいじゃん」と思うじゃん?
zshだけは起動ファイル~/.zshrc
や.
でファイルを読んだ時にそのファイル名を表してしまう。
# echo0.source echo $0
# bash $ . ./echo0.source bash # ksh $ . ./echo0.source ksh # sh $ . ./echo0.source sh # dash $ . ./echo0.source dash # zsh だけ違う出力をする! % . ./echo0.source ./echo0.source
こういった挙動の違いは.bashrc
や.zshrc
を共通のファイルにしている人(そんなにいないかも🤔)にとっては困る。
どんなシェルでも同じ方法でいまのシェルを知る方法が欲しい。
いまのシェル名はps
から取得しよう
僕が思いついた方法はps
を使うことだ。
インターネットで調べると同じようにps
を使っていまのシェルを知る方法を紹介しているページがいくつか見つかる。
ps -o comm= -p $$ # or ps -o args= -p $$
$ bash -i $ ps -o comm= -p $$ bash $ ps -o args= -p $$ bash -i
-o args=
の場合は起動時の引数も表示するという違いがある。
これはPOSIXに規定されているコマンドオプションのみなのでLinux、macOS、確認してないがほかのUnixでも動くだろう。 細かいオプションの説明はしないのでPOSIXのページでも見てほしい。
ちなみに$$
はシェルが展開する変数だ。だから例えばGoのos/exec
パッケージはシェルを経由しないで実行するので
exec.Command("ps", "-o", "comm=", "-p", "$$")
とやっても動かない(シェルが展開するグロブや変数を知らない人を稀によく観察する)。
実はps -o comm= -p $$
が動かない環境がある。
busyboxのpsは-p
オプションがない。
だから上の方法はAlpine Linuxでは動かない。
だから
ps -o pid=,comm= | awk -v pid=$$ '$1 == pid { print $2 }'
となるかなぁ。ちょっとめんどくさいね。