タイトルのとおりですが ksh(KornShell)では環境変数を readonly にすると子プロセス(正確には子プロセスが ksh の場合)にも引き継がれて readonly のままになっています。なんだこれは?とハマったので。ネットを探してもほとんど情報がないです。(ここ「Remove the magic A__z
environment var」ぐらい)
2024-03-22追記 この記事の対象シェルは ksh93 系で ksh93u+m 系は対象外です。
子プロセスの ksh に変数の属性が継承される機能には問題があるとして ksh93u+m 1.0 の POSIX モードで無効になり 1.1 で削除される予定となっています。また readonly
属性に関しては子プロセスで値を初期化するのが不可能となりセキュリティ上の問題があるとして先行してすでに無効になっています。
検証コード
$ ksh
$ readonly VAR=123
$ export VAR
$ ksh # 新たなkshを起動
$ VAR=456
ksh: VAR: is read only # readonlyになってる
もちろん OS としては環境変数に readonly なんてものはないので、子プロセスが ksh 以外だと普通に書き換えられます。ただし孫プロセスが ksh だと readonly 扱いです。(笑)
$ ksh
$ readonly VAR=123
$ export VAR
$ dash
$ echo $VAR
123
$ VAR=456 # dashで書き換えられる
$ ksh
$ echo $VAR
456
$ VAR=789
ksh: VAR: is read only # 書き換えられたのに readonly らしい
どうやって実現してるんだ?と少し考えて思い出したのが BASH のシェル関数のエクスポートの仕組みです。
bashではシェル関数を子プロセス(当然 bash に限る)にエクスポートできます。例えば export -f foo
でシェル関数 foo
をエクスポートすると BASH_FUNC_foo%%
という環境変数に () { echo foo; }
という値(コード)が代入されてエクスポートとされます。(注意 dash だとこの環境変数は見えません。おそらく環境変数名に %%
という文字が含まれているからではないかと。POSIX 的には不正だと思うのですが動かない OS はないのでしょうか? ※コメント参照)
同様に ksh では A__z
という環境変数に(複数の)環境変数の属性が代入されていました。
$ ksh
$ readonly VAR=123
$ export VAR
$ env | grep VAR
VAR=123
A__z="*SHLVL=! VAR
$ typeset -i VAR # VARを数値型にしてみる
$ env | grep VAR
VAR=123
A__z="*SHLVL=#*VAR
A__z
環境変数の値の意味はよくわかりませんがソースコードをざっと見る限り、typeset
で指定できる属性が格納されるようですね。(ちなみに const char e_envmarker[] = "A__z";
)
stakputs(e_envmarker);
tell = staktell();
nv_scan(shp->var_tree, attstore,(void*)0,0 (NV_RDONLY|NV_UTOL|NV_LTOU|NV_RJUST|NV_LJUST|NV_ZFILL|NV_INTEGER));
そうした理由はわからなくはないですが子プロセス(サブシェルではない)に変数の属性が引き継がれて嬉しいことってなにかあるのでしょうか?
ちなみに私がやりたかったのは、親(ksh)プロセスで readonly にした変数を子(ksh)プロセスで書き換えたかったのです。zsh では typeset +r
で readonly 属性を外せたりするのですが ksh や bash では外せません。そこで考え出したのが env
を使って自分自身を起動し直す方法です。
if [ "${FLAG:-}" != 1 ]; then
typeset +x VAR # readonlyとなってる環境変数VARのエクスポート属性を削除しただの変数にする
exec env VAR="$VAR" FLAG=1 "$0" "$@" # env コマンドの引数でVAR変数を改めて環境変数にして呼び出す
fi
同じようにA__z
環境変数を削除することでも実現できると思います。この場合は属性全てが削除されます。