# 環境変数ってなに? - シェルもどきをgoで自作する #12 ## おさらい これまで - [シェルってなに?コマンドラインインタプリタってなに? - シェルもどきをgoで自作する#1](https://hackmd.io/@jyami/HJzohRn2D) - [コマンドと引数の分解、環境変数PATHから探索、外部コマンドと内部コマンド - シェルもどきをgoで自作する #2](https://hackmd.io/@jyami/HyeSkkThP) - [字句解析その1 - シェルもどきをgoで自作する #3](https://hackmd.io/@jyami/Hk3bWSMQO) - [字句解析その2 - シェルもどきをgoで自作する #4](https://hackmd.io/@jyami/S1BkltxQu) - [リダイレクションってなに? - シェルもどきをgoで自作する #5](https://hackmd.io/@jyami/S113NQx8u) - [リダイレクションの種類 - シェルもどきをgoで自作する #6](https://hackmd.io/@jyami/rJ3XmClqd) - [リダイレクションの構文 - シェルもどきをgoで自作する #7](https://hackmd.io/@jyami/BJ04J2Upd) - [コマンドプロセスを作成する際のファイルディスクリプタの操作 - シェルもどきをgoで自作する #8](https://hackmd.io/@jyami/Hy7nSciMt) - [構文解析と抽象構文木 - シェルもどきをgoで自作する #9](https://hackmd.io/@jyami/ByrK1ajIK) - [パイプってなに? - シェルもどきをgoで自作する #10](https://hackmd.io/@jyami/SkXR3iltK) - [ワイルドカードってなに? - シェルもどきをgoで自作する #11](https://hackmd.io/@jyami/SJghKSl3F) シェルもどき「[oreshell](https://github.com/jyami/oreshell)」を自作している。 前回はワイルドカードの実装をした。 今回は環境変数の話。 oreshellで環境変数を扱えるようにしたい。 (シェル変数は後日。) ## 環境変数とは 概要と目的は、IT用語辞典が簡潔に説明している。 > 環境変数とは、OSが設定値などを永続的に保存し、利用者や実行されるプログラムから設定・参照できるようにしたもの。プログラムの実行時などに必要となる、利用者やコンピュータごとに内容が異なる設定値などを記録するために用いられる。 [IT用語辞典](https://e-words.jp/w/%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0.html#Summary) 仕組みについてはwikipediaを参照。 > 環境変数(かんきょうへんすう、英語: environment variable)はオペレーティングシステム (OS) が提供するデータ共有機能の一つ。OS上で動作するタスク(プロセス)がデータを共有するための仕組みである。特にタスクに対して外部からデータを与え、タスクの挙動・設定を変更するために用いる。 > 一つの環境変数は、変数名とその値をもち、通常「変数名=値」と表記する。 変数名は英数字とアンダースコアで構成される。 値は一般的にはとくに型や構造は定義されておらず、単なる文字列である。 > 環境変数は、各プロセスに付随するデータである。一つのプロセスが複数の環境変数をもつことができる。 あるプロセスに付随している環境変数の総体のことを、環境ということがある。 プロセスは、任意に環境変数を参照して、各種の情報を取得したり、動作を変更したりすることができる。 また、プロセスは、自分の環境において、環境変数を新規に作成したり、既存の環境変数の値を変更したり、環境変数を削除したりできる。 > 環境は、親プロセスから子プロセスに複製されて継承される。 すなわち、子プロセスで環境を変更しない限り、子プロセスの環境は親プロセスのそれと同一の内容である。 また、子プロセスが自分の環境に対して行った変更は、親プロセスの環境に影響しない。 [wikipedia](https://ja.wikipedia.org/wiki/%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0) ざっくり説明すると - プロセスがデータを共有する仕組みである。 - 「変数名=値」で指定する。値は文字列である。 - 各プロセスが環境変数を追加/変更/削除できる。 - プロセスの親子環境を通じてデータ(環境変数)を伝播する。 ### 実際の例 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」は環境変数の追加、変更も行う。 上記の様子をシーケンス図で表す。 ```plantuml title プロセス作成と環境変数の伝播の様子 hide footbox participant 最初のプロセス as init participant 子供プロセス1 as child1 participant 子供プロセス2 as child2 participant 子供プロセス1の子供プロセス as child1_1 activate init init -> init : 環境変数を追加 HOGE="hoge" init -> init : 環境変数を追加 HIGE="hige" create child1 init -> child1 : 子プロセスを生成\n(環境変数[HOGE,HIGE]) create child2 init -> child2 : 子プロセスを生成\n(環境変数[HOGE,HIGE]) activate child2 activate child1 child1 -> child1 : 環境変数を追加 HUGE="huge" child1 -> child1 : 環境変数を変更 HOGE="egoh" create child1_1 child1 -> child1_1 : 子プロセスを生成\n(環境変数[HOGE,HIGE,HUGE]) activate child1_1 init -> init : 全ての環境変数を画面出力 child1 -> child1 : 環境変数を変更 HIGE="egih" child1 -> child1 : 全ての環境変数を画面出力 child2 -> child2 : 全ての環境変数を画面出力 child1_1 -> child1_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) ```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つのプロセスのソースコードは[こちら](https://github.com/jyami/oreshell/commits/v0.7)を参照 ### ちょっと細かい話 このサンプルコードでは、プロセス生成時に引き渡すprocAttrのEnvに ```go= procAttr.Env = os.Environ() ``` で、その時点のすべての環境変数を渡している。 が、goは特に指定しなくても(値がnilでも)、同じ動作を行う。([参考](https://pkg.go.dev/os#ProcAttr)) つまり、わざわざ書く必要はなかったのだが、自分で動作を確認するために書いた。 ### 実行結果 ```shell= $ 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の環境変数対応を行う。