(2025-02-01 初稿)
今朝、Bashを使っていて、ちょっとハマったのでメモを残す。
短いスクリプトなのでグローバル変数を使って、さらっとスクリプトを作ろうと思ったが、関数の中でグローバル変数を変更しようとして、正常動作する場合と動作しない場合があることに気がついた。
さっそくハマったスクリプトを超簡単にしたものを以下に示す。
#!/usr/bin/env bash
GVar=""
my_func() {
GVar+="$1"
}
my_subfunc() {
GVar+="$1"
echo $GVar # Gvarを戻り値に
}
my_func abc
echo '1 my_func abc => '"$GVar"
my_func def
echo '2 my_func def => '"$GVar"
tempvar=$(my_func ghi)
echo '3 $(my_func ghi) => '"$GVar $tempvar"
GVar=$(my_subfunc ghi)
echo '4 $(my_subfunc ghi) => '"$GVar"
このスクリプトのmy_func()関数は、Gvarに文字列を追加するスクリプトである。
このスクリプトを実行すると以下のとおり。
1 my_func abc => abc
2 my_func def => abcdef
3 $(my_func ghi) => abcdef
4 $(my_subfunc ghi) => abcdefghi
1および2でmy_func()を実行したときは、abc、defと順調に文字列が追加されたが、関数を$()のようにサブシェルで呼び出したときは、文字列ghiが追加されない。
原因がわからず、ChatGPTさんにお聞きしたら、以下の回答が。
「$(...) を使って関数を実行すると、関数は サブシェル(別のプロセス)で実行 されます。そのため、サブシェル内でグローバル変数に代入された変更は、元のシェルには反映されません。」
なるほど〜 知らなかった。
ということで、サブシェルでもグローバル変数を変更する場合は、my_subfunc()関数の例のようにechoコマンドなどで戻り値を確実に返し、関数の左辺(受ける変数)をグローバル変数にする必要があるようだ。
ちょっと、$BASHPID変数を用いて、確認するスクリプトをGeminiに教えてもらったので、以下に載せる。
#!/bin/ienv bash
my_func () {
echo "my_func内のBASHPID: $BASHPID"
}
echo "呼び出し前のBASHPID: $BASHPID"
my_func
result=$(my_func) #subfunc
echo "result: $result"
これを実行すると、筆者の試した環境では、$()で関数を呼び出した場合(コマンド置換による呼び出し)では、PIDが1つ大きな数字となっており、異なるプロセスであることが確認できた。
呼び出し前のBASHPID: 61643 my_func内のBASHPID: 61643 result: my_func内のBASHPID: 61644
筆者の場合、複数のグローバル変数を利用していたので、ついついグローバル変数がサブシェル内で変更できないのを忘れて、ハマってしまった。
もっとも、グローバル変数を多用していること自体も問題でもあるんだけどね。
という訳で、忘れないように記事にしておいた。