Try   HackMD

環境変数ってなに? - シェルもどきをgoで自作する #12

おさらい

これまで

シェルもどき「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つのプロセスがあるとする。

  • 最初のプロセス
    • 子供プロセス1
      • 子供プロセス1の子供プロセス
    • 子供プロセス2

「最初のプロセス」が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の子供プロセス」は影響を受けない

環境変数の伝播のサンプルプログラム(go実装)

上記のシーケンスを実現するサンプルプログラムを作った。

最初のプロセス(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

環境変数についてoreshellの対応内容

■ コマンド引数に環境変数を指定(環境変数の値を展開)できる。

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の環境変数対応を行う。