# シェル変数を実装 - シェルもどきをgoで自作する #15
## おさらい
これまで
- [シェルってなに?コマンドラインインタプリタってなに? - シェルもどきを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)
- [環境変数ってなに? - シェルもどきをgoで自作する #12](https://hackmd.io/@jyami/H12wYg4L9)
- [環境変数の設定と環境変数の展開 - シェルもどきをgoで自作する #13](https://hackmd.io/@jyami/Hy3EefRgj)
- [シェル変数との違いからみた環境変数 - シェルもどきをgoで自作する #14](https://hackmd.io/@jyami/ByxFrm6Si)
シェルもどき「[oreshell](https://github.com/jyami/oreshell)」を自作している。
前回はシェル変数と環境変数の違いを確認した。
今回はoreshellでシェル変数を実現する。
oreshellでシェル変数を実現するためには以下の機能が必要である。
- シェル変数に値を設定
- シェル変数を展開
- シェル変数の一覧を表示
- シェル変数の削除
- その他(コマンド実行時に環境変数を指定する)
## 機能の追加、変更の内容
### シェル変数に値を設定
posixシェルでシェル変数に値を設定する方法は以下のコマンドで行う。
```
<シェル変数名>=<値>
```
以下はコマンド例
```
> HOGE=hoge
> HOGE=hoge HIGE=hige
```
この変更に対応するために構文を変更する。
構文はいつものようにrailroad diagramで表す。
以下がシェル変数対応前の構文

cmd_name はコマンド名を表す。
cmd_suffixはコマンド引数、リダイレクションを表す。
(パイプに対応しているけどここでは省略)
これを以下のように変更する。↓

assignment_word は変数への設定を表す。(<変数名>=<値>)
### シェル変数を展開
posixシェルでシェル変数を展開する方法は以下のコマンドで行う。
```
$シェル変数名
または
${シェル変数名}
```
以下はコマンド例
```
> echo $HOGE
hoge
> echo ${HIGE}
hige
```
### シェル変数一覧を表示
posixシェルでシェル変数一覧を表示する方法は以下の内部コマンドで行う。
```
set
```
以下はコマンド例
```
> set
HOGE=hoge
HIGE=hige
```
■ envコマンドとの違い
envコマンドは似たような動作するがこちらは環境変数の一覧を表示する。
setコマンドはシェル変数の一覧を表示する。
### シェル変数を削除
posixシェルでシェル変数を削除する方法は以下のコマンドで行う。
```
<シェル変数名>=
```
または以下の内部コマンドで行う。
```
unset <シェル変数名>
```
以下はコマンド例
```
> HOGE=
> unset HIGE
```
### その他(コマンド実行時に環境変数を指定する)
ところで、bashなどは以下の構文も対応している。

つまり
```
<変数名>=<値> <コマンド名> <コマンド引数>
```
これは、「コマンド実行時に環境変数を指定する」方法である。
(この方法そのものはシェル変数とは関係がない。たぶん。)
コマンド例
```
> PGPASSWORD=<パスワード> psql -U <ユーザ名> <データベース名>
```
psqlコマンドは環境変数「PGPASSWORD」でパスワードを指定してコマンドを実行することができる。([参考](https://www.postgresql.jp/document/8.1/html/libpq-envars.html))
今回の変更では「コマンド実行時に環境変数を指定する」にも対応する。
## 修正箇所
今回のおおよその修正範囲は以下の通り。

## 実装
[こちら](https://github.com/jyami/oreshell/commits/v0.8)を参照。
### シェル変数をどうやって実現するか
インタプリタを簡易実装するときは、変数を実現するためにハッシュマップを利用することが多い。
oreshellのシェル変数も[ハッシュマップで実現する](https://github.com/jyami/oreshell/blob/v0.8/myvariables/variables.go#L8)。
以下、ソースコードから抜粋。
```go
var shellVariables = map[string]string{}
```
posixシェルは変数名、値がともに文字列である。oreshellもそれに倣う。
### コマンド実行時に環境変数を指定する
posixではプロセス生成時に環境変数を指定することができる。
通常、特に指定しなければ親プロセスの環境変数を引き継ぐ。
go言語の場合、プロセス起動時にos.ProcAttrのEnvに環境変数を指定することで実現できる。([参考](https://pkg.go.dev/os#ProcAttr))
ただし、親プロセスの環境変数も受け継いでほしいので、指定したい環境変数と親プロセス(シェル)の環境変数をマージして指定する必要がある。(これがめんどくさい。。。)
```go
func (me *Process) createProcAttrEnv() (env []string) {
assignVariableParser := myvariables.NewAssignVariableParser()
// シェルプロセスの環境変数とユーザが設定した環境変数をマージ
for _, v := range os.Environ() { // os.Environ()は<変数名>=<値>の文字列配列を返す
_, name, value := assignVariableParser.TryParse(v)
// me.variablesMapはユーザが設定した環境変数
_, ok := me.variablesMap[name]
if !ok { // ユーザ設定値を上書きしない
me.variablesMap[name] = value
}
}
// 環境変数のハッシュマップを配列に変換
ar := []string{}
for k, v := range me.variablesMap {
ar = append(ar, fmt.Sprintf("%s=%s", k, v))
}
return ar
}
func (me *Process) Start() (err error) {
var procAttr os.ProcAttr
if me.variablesMap != nil || len(me.variablesMap) > 0 {
procAttr.Env = me.createProcAttrEnv()
}
(略)
me.osProcess, err = os.StartProcess(me.command, me.argv, &procAttr)
(略)
```
## 実行例
```shell
# シェル変数HIGE,HUGEに値を設定
(ore) > HIGE=hige HUGE=huge
# シェル変数HIGEを展開
(ore) > echo $HIGE
hige
# シェル変数HUGEを展開
(ore) > echo $HUGE
huge
# HOGEはシェル変数ではない
(ore) > echo $HOGE
# シェル変数一覧を表示
(ore) > set
HIGE=hige
HUGE=hugu
# シェル変数HIGEを削除
(ore) > HIGE=
(ore) > set
HUGE=hugu
# シェル変数HUGEを削除
(ore) > unset HUGE
(ore) > set
```
「コマンド実行時に環境変数を指定する」について[awscli](https://aws.amazon.com/jp/cli/)コマンドを使って確認する。
awscliはコマンドラインパラメータ「--region」、または環境変数「AWS_REGION」で接続先リージョンを切り替えることができる。([参考](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-envvars.html))
```shell
# 接続先リージョンap-northeast-1を環境変数で指定
(ore) > AWS_REGION=ap-northeast-1 aws cognito-idp list-user-pools --max-results 1
{
"UserPools": [
{
"Id": "ap-northeast-1_*****",
"Name": "****",
(略)
}
# 接続先リージョンus-west-2を環境変数で指定
(ore) > AWS_REGION=us-west-2 aws cognito-idp list-user-pools --max-results 1
{
"UserPools": [
{
"Id": "us-west-2_******",
"Name": "*****",
(略)
}
```
## 次回以降
バックグラウンド実行、ジョブ制御を予定。