ひゃまだのblog

ひゃまだ(id:hymd3a)の趣味のブログ

Bashでサブシェルの中でのグローバル変数の変更に注意

(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

筆者の場合、複数のグローバル変数を利用していたので、ついついグローバル変数がサブシェル内で変更できないのを忘れて、ハマってしまった。

もっとも、グローバル変数を多用していること自体も問題でもあるんだけどね。

という訳で、忘れないように記事にしておいた。

関連ページ