Bash 5.0では、namerefの名前解決で非互換の挙動があると言われています。
リリースアナウンスでは次のように書かれています。
There are a few incompatible changes between bash-4.4 and bash-5.0. The
changes to how nameref variables are resolved means that some uses of
namerefs will behave differently, though I have tried to minimize the
compatibility issues.
「bash 5.0のNEWSファイル私訳」にも次のようにあります。
i. 関数内でnamerefの名前解決のループは、グローバルスコープでの名前の変数
に解決されるようになりました。
そもそもnamerefを知らないと変更点もわからないので、順に説明します。
おさらい①:namerefって?
namarefはBash 4.3から導入された機能です。ksh由来のようで、AIXのマニュアルでは「名前参照」と、Solarisのマニュアルでは「nameref」と表記されているようです。
namerefは、ほかの変数を参照する変数を作る機能です。変数のエイリアス(別名)を付けるときなどに使います。
namerefはdeclare -n
やlocal -n
など、変数宣言のときに-n
オプションを指定して作ります。namerefな変数に、ほかの変数名を文字列として代入すると、エイリアスのように動きます。
$ aaa=3 # 変数aaaに3を代入
$ declare -n bbb # namerefな変数bbbを宣言
$ bbb=aaa # bbbに'aaa'を代入($aaaではない)
$ echo $bbb # bbbの内容は?
3
おさらい②:namerefの使い道
namerefの使い道として自分が考えたのが、関数から関数に値を渡すときに、変数名を渡す方法です。
たとえば、関数から関数に配列を渡すときには、引数や標準入力ではそのままでは渡せません。そこでダイナミックスコープを使って渡す方法があります。
foo() {
local ary=('a b' c d) # 配列変数aryを宣言
bar
}
bar() {
echo ${ary[0]} # fooで宣言された配列変数aryを参照
}
foo # 「a b」と表示される
ただbarから暗黙的に変数aryを参照するのがいまいちです。そこで、namerefを使って変数名を渡すことで、barではfooで宣言された変数名の知識が不要になります。
foo() {
local ary=('a b' c d)
bar ary # “ary”という変数名を文字列で引数に
}
bar() {
local -n var=$1 # nameref変数varに1つめの引数の文字列(変数名)を代入
echo ${var[0]}
}
foo # 「a b」と表示される
namerefの挙動の違い
さて、Bash 5.0で挙動が変わったのはどのあたりでしょうか。
「関数内でのnamerefの名前解決のループ(A nameref name resolution loop in a function)」ということなので、namerefで循環参照を作ってみます。
aaa=7 # グローバル変数aaa
foo() {
local -n aaa # ローカルなnameref変数aaa
aaa=aaa # aaaからaaa自身を参照
echo $aaa
}
foo
これをBash 4.4で実行すると、循環参照の警告が表示されたあと、echo $aaa
では何も表示されません。
$ bash ./n.sh
./n.sh: line 6: warning: aaa: circular name reference
Bash 5.0で実行すると、循環参照の警告が表示されたあと、echo $aaa
でグローバル変数aaaの値である「7」が表示されます。
$ ./bash ./n.sh
./n.sh: line 6: warning: aaa: circular name reference
7
結論
そもそも、Bashでnamerefをそれほど使うとは思わないし、その中でも特殊なケースなので、非互換による影響はほとんどないのではないでしょうか。