# 環境変数ってなに? - シェルもどきを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の環境変数対応を行う。