bashのちょっとしたバグと環境変数の話

変数の処理に関わるbashのバグっぽい挙動と、そこから推測される「環境変数」の扱いについての考察をまとめたもの。
7
angel (as ㌵㌤の猫) @angel_p_57

昨日調べたこの話題、実用性があるわけではないけど、シェルの動きを考える上ではなかなか興味深いものだった。まあ、記事にする程でもないので、連ツイで。 twitter.com/sukesan1984/st…

2021-02-23 18:57:14
Sukesan1984 @sukesan1984

Quoraで質問して回答をいただき納得した気がする。 Kohei Yoshitoimiさんによる「bashで`HOME=/ cd`とすると"/"に移動し、`HOME=/ export`とすると環境変数HOMEは"/"に書き換わっていないのはなぜなのでしょうか?」への回答 jp.quora.com/bash%E3%81%A7H…

2021-02-22 11:39:51
angel (as ㌵㌤の猫) @angel_p_57

まず、このbashの挙動自体は単純にバグで良いと思う。実用上何か困るってことはないけど、挙動が一貫していないという意味で。

2021-02-23 18:57:51
angel (as ㌵㌤の猫) @angel_p_57

一応挙動を簡単に言うと、"KEY=val declare -xp" のように変数を指定した上で変数一覧を見るような「内部」コマンドを実行しても、この変数が反映されていないということになる。

2021-02-23 18:58:23
angel (as ㌵㌤の猫) @angel_p_57

先に前提として、"KEY=val command" の形式で「外部」コマンドを実行した場合、起動するプログラムに渡される環境変数として、この KEY の分も追加/修正される。それを念頭に置いて。

2021-02-23 18:58:44
angel (as ㌵㌤の猫) @angel_p_57

同じようにと言うか、内部コマンドであっても、例えば "IFS=○ read var" だと IFS への値の設定が read の挙動に影響を与える。

2021-02-23 18:59:09
angel (as ㌵㌤の猫) @angel_p_57

そうすると、"KEY=val declare -xp" という変数の一覧を出力するコマンドでは "declare -x KEY="val"" という出力が含まれることを自然と期待する。しかしそうではないという挙動になっている。

2021-02-23 19:00:02
angel (as ㌵㌤の猫) @angel_p_57

ところがちょっとコマンド替えると出力に含まれるようになる。例えば "KEY=val declare -xp KEY" がその一つ。これは変数一覧ではなく単体で出力するコマンドだ。

2021-02-23 19:00:19
angel (as ㌵㌤の猫) @angel_p_57

もう一つ、同じ一覧出力でもシェル関数を経由すれば含まれるようになる。例えば "mydec () { declare "$@"; }; KEY=val mydec -xp" がそうだ。

2021-02-23 19:00:42
angel (as ㌵㌤の猫) @angel_p_57

「挙動が一貫していない」というのはこのことを指す。そういう意味でバグと言ってよいだろう。影響範囲としては、declare と export、それから set あたりになるように思う。

2021-02-23 19:00:58
angel (as ㌵㌤の猫) @angel_p_57

ソースまで読んだわけではないので推測だけど、"KEY=val 内部コマンド or 関数" の形式の場合、外部コマンドの場合と違って、新しいプロセスを生成せず今のシェルの中で処理を進める、そこに鍵がある。

2021-02-23 19:01:18
angel (as ㌵㌤の猫) @angel_p_57

つまり、新しいプロセスに変数の変更を全て押し付けてシェル自身はそのままの状態を保てる外部コマンド実行と違い、変数の状況を一時的に変更して後で復帰する必要がある。そこの処理で出た問題ということになる。

2021-02-23 19:01:44
angel (as ㌵㌤の猫) @angel_p_57

"KEY=val declare -xp" と "KEY=val declare -xp KEY" で、違いが出るということは、「単一の変数参照」は自然な挙動だけども、「変数一覧の取得」に問題があると推定できる。

2021-02-23 19:02:05
angel (as ㌵㌤の猫) @angel_p_57

ここまでがバグの話。ただ、これ妙に思う人もいるかも知れない。「環境変数を設定してから内部コマンドの処理を実行して、終わってから復帰させるだけなのに、なんでバグ出してるの?」と。

2021-02-23 19:02:38
angel (as ㌵㌤の猫) @angel_p_57

で、ここからが本題。「環境変数に見えるものが実際の環境変数ではない」という話になる。

2021-02-23 19:02:56
angel (as ㌵㌤の猫) @angel_p_57

よく、「"export KEY=val" とすると、KEYという環境変数が設定されますよ」という説明をする。それが間違っているわけではないんだけど、内部的にはそう単純ではない、ということ。

2021-02-23 19:03:22
angel (as ㌵㌤の猫) @angel_p_57

おさらいとして「環境変数とは何か」というと、標準ライブラリ libc で管理している、シンボル environ を通じて管理されている文字列データの集合体であって、getenv や setenv といったC言語APIで操作できるものだ。

2021-02-23 19:03:51
angel (as ㌵㌤の猫) @angel_p_57

「いやでも、Pythonだと os.environ、Rubyだと ENV というオブジェクトで操作できるのでは」と思うかも知れないけど、これは裏でそういったC言語APIを呼んで辻褄を合わせているから。

2021-02-23 19:04:10
angel (as ㌵㌤の猫) @angel_p_57

ところが、bash で "export KEY=val" を実行しても、そういう意味でのbashプロセス自身の環境変数には反映されない。いや、後で反映されるタイミングがあるんだけど、少なくとも即時ではない。

2021-02-23 19:04:38
angel (as ㌵㌤の猫) @angel_p_57

では、この時の KEY ってなんだと言われると、「エクスポート属性のついたシェル変数 KEY」ということになる。エクスポート属性は、外部コマンド実行時に環境変数として渡されるという効果を表すものだ。

2021-02-23 19:04:53
angel (as ㌵㌤の猫) @angel_p_57

つまり「exportは環境変数を設定しますよ」というのは、実行される外部コマンドの立場ではその通りだけど、bash自身としてはあくまで「環境変数として引き渡す予定のデータ」でしかない、ということで微妙に違うことになる。

2021-02-23 19:05:18
angel (as ㌵㌤の猫) @angel_p_57

尤も、bash が外部コマンドを実行する時は、どうやら「自分の環境変数を直接渡す」という方法を採っているようなので、結局「bash自身の環境変数」にもなるようだが。「後で反映されるタイミングがある」と言ったのはそういうこと。

2021-02-23 19:05:41
angel (as ㌵㌤の猫) @angel_p_57

※これについては、内部コマンド exec の挙動しか見てないので、絶対そうかと言われると自信はない。

2021-02-23 19:05:58
angel (as ㌵㌤の猫) @angel_p_57

じゃあ、なんでそんな違いを設けているのか? ということになるけど、おそらく「環境変数として管理するメリットが無いから」なんだろうと思う。ここが、今回の問題を考察してみて気付いた点だ。

2021-02-23 19:06:11
angel (as ㌵㌤の猫) @angel_p_57

ということはデメリットがあるということになる。まず1つ目は変数の情報が二重管理になる点だ。

2021-02-23 19:06:33
angel (as ㌵㌤の猫) @angel_p_57

例えば、内部コマンド cd は HOME変数の、read は IFS変数の影響を受ける。「環境変数」ではなく「シェル変数」になっている。

2021-02-23 19:06:59