これまで
シェルもどき「oreshell」を自作している。
前回はワイルドカードの実装をした。
今回は環境変数の話。
oreshellで環境変数を扱えるようにしたい。
(シェル変数は後日。)
概要と目的は、IT用語辞典が簡潔に説明している。
環境変数とは、OSが設定値などを永続的に保存し、利用者や実行されるプログラムから設定・参照できるようにしたもの。プログラムの実行時などに必要となる、利用者やコンピュータごとに内容が異なる設定値などを記録するために用いられる。
IT用語辞典
仕組みについてはwikipediaを参照。
環境変数(かんきょうへんすう、英語: environment variable)はオペレーティングシステム (OS) が提供するデータ共有機能の一つ。OS上で動作するタスク(プロセス)がデータを共有するための仕組みである。特にタスクに対して外部からデータを与え、タスクの挙動・設定を変更するために用いる。
一つの環境変数は、変数名とその値をもち、通常「変数名=値」と表記する。 変数名は英数字とアンダースコアで構成される。 値は一般的にはとくに型や構造は定義されておらず、単なる文字列である。
環境変数は、各プロセスに付随するデータである。一つのプロセスが複数の環境変数をもつことができる。 あるプロセスに付随している環境変数の総体のことを、環境ということがある。 プロセスは、任意に環境変数を参照して、各種の情報を取得したり、動作を変更したりすることができる。 また、プロセスは、自分の環境において、環境変数を新規に作成したり、既存の環境変数の値を変更したり、環境変数を削除したりできる。
環境は、親プロセスから子プロセスに複製されて継承される。 すなわち、子プロセスで環境を変更しない限り、子プロセスの環境は親プロセスのそれと同一の内容である。 また、子プロセスが自分の環境に対して行った変更は、親プロセスの環境に影響しない。
wikipedia
ざっくり説明すると
linux/bsd/osxはenvコマンドを実行すると、現在のシェルプロセスの環境変数を見ることができる。
$ env
SHELL=/bin/bash
HOME=/home/oresama
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
...
oreshellに環境変数の対応を行う前に、親子プロセス間の環境変数の伝播について確認する。
以下の4つのプロセスがあるとする。
「最初のプロセス」が2つの子供のプロセスを作成、そのうちの一つが子供のプロセスを作成する。
「最初のプロセス」が環境変数を作成しそれを子供に伝播し、子供が孫に伝播する。
「子プロセス1」は環境変数の追加、変更も行う。
上記の様子をシーケンス図で表す。
「最初のプロセス」が全ての環境変数を画面出力した結果
名前 | 値 |
---|---|
HOGE | hoge |
HIGE | hige |
「子供プロセス1」が全ての環境変数を画面出力した結果
名前 | 値 |
---|---|
HOGE | egoh |
HIGE | egih |
HUGE | huge |
「子供プロセス2」が全ての環境変数を画面出力した結果
名前 | 値 |
---|---|
HOGE | hoge |
HIGE | hige |
「子供プロセス1の子供プロセス」が全ての環境変数を画面出力した結果
名前 | 値 |
---|---|
HOGE | egoh |
HIGE | hige |
HUGE | huge |
通常、Unix(の影響を受けたOS)でプロセスを作成するときは、上記の通り「作成する子プロセスに自身の環境変数を渡す」ことが一般的であるため、4つのプロセスは環境変数を共有することができる。伝播の途中で環境変数を追加したり値を書き換えることもできる。(伝播しないこともできる。)
「子供プロセス1」が行った環境変数の追加/変更について、「最初のプロセス」「子供プロセス2」は影響を受けない。
また、「子供プロセス1」が「子供プロセス1の子供プロセス」を作成した後に行った環境変数の変更について、「子供プロセス1の子供プロセス」は影響を受けない。
上記のシーケンスを実現するサンプルプログラムを作った。
最初のプロセス(init.go)
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Printf("usage: init <proc-attr-env_sample FullPath>\n")
return
}
parentPath := os.Args[1]
os.Clearenv()
os.Setenv("HOGE", "hoge")
os.Setenv("HIGE", "hige")
var procAttr os.ProcAttr
procAttr.Env = os.Environ()
procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr}
path := parentPath + "/child1/child1"
process1, e := os.StartProcess(path, []string{path, parentPath}, &procAttr)
if e != nil {
panic(e)
}
path = parentPath + "/child2/child2"
process2, e := os.StartProcess(path, nil, &procAttr)
if e != nil {
panic(e)
}
for _, s := range os.Environ() {
fmt.Printf("init:%s\n", s)
}
process1.Wait()
process2.Wait()
}
残り3つのプロセスのソースコードはこちらを参照
このサンプルコードでは、プロセス生成時に引き渡すprocAttrのEnvに
procAttr.Env = os.Environ()
で、その時点のすべての環境変数を渡している。
が、goは特に指定しなくても(値がnilでも)、同じ動作を行う。(参考)
つまり、わざわざ書く必要はなかったのだが、自分で動作を確認するために書いた。
$ init/init `pwd` # 「最初のプロセス」を起動して、他の3つのプロセスも順次起動
init:HOGE=hoge
init:HIGE=hige
child2:HOGE=hoge
child2:HIGE=hige
child1:HOGE=egoh
child1:HIGE=egih
child1:HUGE=huge
child1_1:HOGE=egoh
child1_1:HIGE=hige
child1_1:HUGE=huge
■ コマンド引数に環境変数を指定(環境変数の値を展開)できる。
bashの例
$ echo ${PATH}
■ シェルから環境変数を追加、変更、削除できる。
bashの例
$ export HOGE=hoge
■ 一時的に環境変数を指定してプログラムを実行できる。
bashの例
$ vi hige.sh
echo ${HIGE}
$ HIGE=hige ./hige.sh
hige
これはわざわざ作らなくてもenvコマンドで同じことができる。
$ vi hige.sh
echo ${HIGE}
$ env HIGE=hige ./hige.sh
hige
bashに用意されているのでoreshellでもやれるかどうか試してみる。
次回からoreshellの環境変数対応を行う。