Unengineered Weblog

PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND

環境変数$SHELLの使い方を間違っていませんか?

要約

いま動いているインタラクティブシェルを知る方法に環境変数$SHELLを読むのが広く知られているが、これはこの環境変数の誤った使い方である。 $SHELLはユーザーのお気に入りのシェルを指定するものである($EDITORに近い)。 シェルは起動時に$SHELLの値をいまのシェルにセットするわけではない。 いま動いているシェルを知るには

ps -o comm= -p $$

とするのが良さそうだ。

Googleで「現在のシェル 確認」と検索すると

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.

pubs.opengroup.org

この説明からわかるように$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

このようにbashksh環境変数$SHELL/bin/bashに上書きしてくれるわけでは無い。 このようにいま動いているシェルと$SHELLが異なる状態で$SHELLからいま動いているシェルを知ろうとする(行儀の悪い)スクリプトsource.で読むとおかしい挙動をしたりする。

だったらどうやっていまのシェルを調べればよいのだろう。

$0は罠がある

$0C言語での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に規定されているコマンドオプションのみなのでLinuxmacOS、確認してないがほかのUnixでも動くだろう。 細かいオプションの説明はしないのでPOSIXのページでも見てほしい。

pubs.opengroup.org

ちなみに$$はシェルが展開する変数だ。だから例えば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 }'

となるかなぁ。ちょっとめんどくさいね。