Bashの変数スコープについてのメモ

# 要旨
* 変数は基本的にグローバル変数である。同じプロセス内で参照・変更ができる。
* サブシェル実行した場合、元プロセスのグローバル変数を参照・変更できる。ただし、値の変更は元プロセスに伝播しない。
* スクリプト内でsh実行した場合、`export`された元プロセスのグローバル変数のみ参照・変更できる。ただし、値の変更は元プロセスに伝播しない。
* 関数内において`local`宣言された変数はその関数内でのみ有効で、関数外には持ち出されない。

# 検証
### 環境
* Ubuntu 18.04 (WSL)
* bash 4.4.19

## bashの実行プロセスについて
本題に入る前に子シェルや関数の実行プロセスについて確認する。
### 関数の実行

```bash:main1.sh
#!/bin/bash

echo "ベタ書き"
echo "main PID/BASHPID/PPID=$$/$BASHPID/$PPID"

func1(){
        echo "func1 PID/BASHPID/PPID=$$/$BASHPID/$PPID"
}

echo "関数呼び出し"
func1

echo "関数呼び出し(サブシェル)"
(func1)

echo "パイプ実行"
func1 >&2 | func1

echo "連続実行"
func1 && func1
```
実行結果

```
$ ./main1.sh
ベタ書き
main PID/BASHPID/PPID=376/376/4
関数呼び出し
func1 PID/BASHPID/PPID=376/376/4
関数呼び出し(サブシェル)
func1 PID/BASHPID/PPID=376/377/4
パイプ実行
func1 PID/BASHPID/PPID=376/378/4
func1 PID/BASHPID/PPID=376/379/4
連続実行
func1 PID/BASHPID/PPID=376/376/4
func1 PID/BASHPID/PPID=376/376/4
```
PID, PPIDは全パターンで同じであるが、サブシェル実行とパイプラインを使用して実行した場合はBASHPIDが変わっている。

### スクリプト内でのbash実行

```bash:main2.sh
#!/bin/bash

echo "ベタ書き"
echo "main PID/BASHPID/PPID=$$/$BASHPID/$PPID"

echo "bash実行"
./sub.sh
```
```bash:sub.sh
#!/bin/bash

echo "sub PID/BASHPID/PPID=$$/$BASHPID/$PPID"

sfunc(){
        echo "sfunc PID/BASHPID/PPID=$$/$BASHPID/$PPID"
}

sfunc
```
実行結果

```
$ ./main2.sh
ベタ書き
main PID/BASHPID/PPID=234/234/4
bash実行
sub PID/BASHPID/PPID=235/235/234
sfunc PID/BASHPID/PPID=235/235/234
```
sub.shには新しいPIDが発行され、PPIDにはmain2.shのPIDが格納されている。

### source実行

```bash:main3.sh
#!/bin/bash

echo "ベタ書き"
echo "main PID/BASHPID/PPID=$$/$BASHPID/$PPID"

echo "source実行"
. inc.sh

echo "関数呼び出し"
ifunc
```
```bash:inc.sh
#!/bin/bash

echo "inc PID/BASHPID/PPID=$$/$BASHPID/$PPID"

ifunc(){
        echo "ifunc PID/BASHPID/PPID=$$/$BASHPID/$PPID"
}
```
実行結果

```
$ ./main3.sh
ベタ書き
main PID/BASHPID/PPID=244/244/4
source実行
inc PID/BASHPID/PPID=244/244/4
関数呼び出し
ifunc PID/BASHPID/PPID=244/244/4
```
すべての値が同じになっている。つまり、inc.shの内容をmain3.shに直接書いた場合と同じである。

## 変数の有効範囲について
### 同PIDの場合

```bash:main1.sh
#!/bin/bash

var_m="M"
. inc2.sh
echo "初期状態"
echo "main var_m/var_i=${var_m}/${var_i}"

func1(){
        var_m="${var_m}m"
        var_i="${var_i}m"
        echo "func1 var_m/var_i=${var_m}/${var_i}"
}

echo "関数呼び出し"
func1
echo "main var_m/var_i=${var_m}/${var_i}"

echo "関数呼び出し(サブシェル)"
(func1)
echo "main var_m/var_i=${var_m}/${var_i}"

echo "inc内関数呼び出し"
ifunc
echo "main var_m/var_i=${var_m}/${var_i}"
```
```bash:inc.sh
#!/bin/bash

var_i="I"

ifunc(){
        var_m="${var_m}i"
        var_i="${var_i}i"
        echo "func1 var_m/var_i=${var_m}/${var_i}"
}
```
実行結果

```
$ ./main1.sh
初期状態
main var_m/var_i=M/I
関数呼び出し
func1 var_m/var_i=Mm/Im
main var_m/var_i=Mm/Im
関数呼び出し(サブシェル)
func1 var_m/var_i=Mmm/Imm
main var_m/var_i=Mm/Im
inc内関数呼び出し
func1 var_m/var_i=Mmi/Imi
main var_m/var_i=Mmi/Imi
```
同じBASHPIDで動いている箇所では読み書きができる。これはsourceで取り込んだ変数・関数においても同様である。
サブシェルでは変数の参照はできるが、変更は元シェルには伝播しない。

### スクリプト内でbash実行

```bash:main2.sh
#!/bin/bash

var_m="M"
var_e="E"
export var_e

echo "初期状態"
echo "main var_m/var_e=${var_m}/${var_e}"

echo "bash実行"
./sub.sh
echo "main var_m/var_e=${var_m}/${var_e}"
```
```bash:sub.sh
#!/bin/bash

echo "sub var_m/var_e=${var_m}/${var_e}"

sfunc(){
        var_m="${var_m}s"
        var_e="${var_e}s"
        echo "sfunc var_m/var_e=${var_m}/${var_e}"
}

sfunc
```
実行結果

```
$ ./main2.sh
初期状態
main var_m/var_e=M/E
bash実行
sub var_m/var_e=/E
sfunc var_m/var_e=s/Es
main var_m/var_e=M/E
```
exportした変数のみ子シェルからも参照することができる。ただし変更は元シェルには伝播しない。

#### 関数内での宣言
```bash:main3.sh
#!/bin/bash

func1(){
        var_g="G"
        local var_l="L"
        echo "func1 var_g/var_l=${var_g}/${var_l}"
}

func1
echo "main var_g/var_l=${var_g}/${var_l}"
```
実行結果

```
$ ./main3.sh
func1 var_g/var_l=G/L
main var_g/var_l=G/
```
関数内が初出の変数もプロセス全体で使用できる。ただしlocalをつけて宣言した場合は関数のみで有効になる。

### （おまけ）定数の宣言
readonlyをつけて宣言および代入をする。この変数には再代入ができなくなる。

```bash:main4.sh
#!/bin/bash

readonly var_r="R"
var_r="${var_r}r"
echo "main var_r=${var_r}"
```
実行結果

```
$ ./main4.sh
./main4.sh: line 4: var_r: readonly variable
main var_r=R
```

## 参考サイト
* [bashでのPID取得方法まとめ($$、$PPID、$!、$BASHPID) - Qiita](https://qiita.com/laikuaut/items/1daa06900ad045d119b4)
* [bashで定数やローカル変数を宣言する方法 - 感謝のプログラミング 10000時間](http://blog.codebook-10000.com/entry/20140303/1393850240)