要旨
- 変数は基本的にグローバル変数である。同じプロセス内で参照・変更ができる。
- サブシェル実行した場合、元プロセスのグローバル変数を参照・変更できる。ただし、値の変更は元プロセスに伝播しない。
- スクリプト内でsh実行した場合、
export
された元プロセスのグローバル変数のみ参照・変更できる。ただし、値の変更は元プロセスに伝播しない。 - 関数内において
local
宣言された変数はその関数内でのみ有効で、関数外には持ち出されない。
検証
環境
- Ubuntu 18.04 (WSL)
- bash 4.4.19
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実行
main2.sh
#!/bin/bash
echo "ベタ書き"
echo "main PID/BASHPID/PPID=$$/$BASHPID/$PPID"
echo "bash実行"
./sub.sh
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実行
main3.sh
#!/bin/bash
echo "ベタ書き"
echo "main PID/BASHPID/PPID=$$/$BASHPID/$PPID"
echo "source実行"
. inc.sh
echo "関数呼び出し"
ifunc
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の場合
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}"
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実行
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}"
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した変数のみ子シェルからも参照することができる。ただし変更は元シェルには伝播しない。
関数内での宣言
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をつけて宣言および代入をする。この変数には再代入ができなくなる。
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