Edited at

Bash 5.0の新機能:namerefの挙動の変更

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 -nlocal -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をそれほど使うとは思わないし、その中でも特殊なケースなので、非互換による影響はほとんどないのではないでしょうか。