bash シェル関数には循環名参照がありますが、ksh にはありません。

bash シェル関数には循環名参照がありますが、ksh にはありません。

BashとKornShell93で動作することを望む一連のシェル関数を書いていますが、Bashを使用すると「循環名を参照」という警告が表示されます。

問題の本質はこうです。

function set_it {
    typeset -n var="$1"

    var="hello:$var"
}

function call_it {
    typeset -n var="$1"

    set_it var
}

something="boff"
call_it something
echo "$something"

実行してください:

$ ksh script.sh
hello:boff

$ bash script.sh
script.sh: line 4: warning: var: circular name reference
hello:

somethingKornShell93は私が望むことを正確に行いますが、Bashは失敗し、スクリプトの変数名が2行目に同じ警告を表示しますvar

変数はすべての関数に対してローカルであることを望んでいるvarので、これを使用typesetしますが、Bashはnameref自体と同じ名前を持つ変数に対してnamerefを「逆参照」するのが好きではないようです。local -nordeclare -nがないと競合が発生するため使用できずksh、そのようにしても問題は解決されません。

私が見つけた唯一の解決策は各関数に固有の変数名を使用してください。、地元なので多少愚かなようです。

Bashのマニュアルには以下が含まれていますtypeset

typeset[...]

-n 各名前にnameref属性を付与して、他の変数への名前参照になるようにしてください。この他の変数はの値として定義されます namename属性自体を変更することに加えて、すべての参照と割り当ては、名前の値 -n で参照される変数に対して行われます。

[...]

関数内で使用するときにこのオプションが指定されていない限り、コマンドを使用するのと同じように、各名前をローカル名に declareします。変数名の後に続く場合、その変数の値はに設定されます。typesetlocal-g=valuevalue

明らかに、Bashの名前参照と関数ローカル変数にはいくつかの問題があります。

したがって、質問は、この場合、Bashの名前参照変数の処理が欠落しているのですか、それともBashのバグ/バグ機能ですか?

修正する:私は現在、GNU bash, version 4.3.39(1)-release (x86_64-apple-darwin15)そして一緒に働いていますGNU bash, version 4.3.46(1)-release (x86_64-unknown-openbsd6.0)。 macOS に付属している Bash は古すぎるため、名前参照についてはわかりません。

修正する:より短く:

function bug {
    typeset -n var="$1"
    printf "%s\n" "$var"
}

var="hello"
bug var

明らかになるbash: warning: var: circular name referencevar機能上範囲が異なるはずですvarグローバルな観点から。これは呼び出し側に不必要な制限を課します。制限は、「この関数の(ローカル)名前参照と名前の競合が発生する可能性があるため、任意に変数名を指定することはできません」です。

答え1

Chet Ramey (Bash マネージャー)説明する

今年初め、バグバスでnamerefについて広範な議論がありました。この動作を変更する方法についての健全な提案があります。 bash-4.4がリリースされた後、それを見てみましょう。

その間、私はローカルのnameref変数の名前を少し難読化して、ライブラリ内で(希望的に)グローバルシェル変数名と衝突しないようにしました。


5.0では、bashこの問題は少し修正されました(しかし実際には修正されませんでした)。観察された動作は次のとおりです。

$ foo () { typeset -n var="$1"; echo "$var"; }
$ var=hello
$ foo var
bash: typeset: warning: var: circular name reference
bash: warning: var: circular name reference
bash: warning: var: circular name reference
hello

これはうまくいきますが、いくつかの注意事項があります。

関連情報項目は言う

i. A nameref name resolution loop in a function now resolves to a variable by
that name in the global scope.

答え2

Bashの名前参照は少し愚かで、上記のものとほぼ正確に一致しています。名前。ほとんどの汎用プログラミング言語では、ポインタや参照など、参照変数の「ID」をキャプチャしません。

declare -n ref=varしたがって、usingを実行すると、ref変数名を見つけてvarその値を使用します。

次のコマンドを実行すると、declare -n foo=foousingはfoo変数の名前検索を実行し、foo...まあ、ループであることを認識し、警告を発行します。

また、参照で見つかった変数は、namerefを設定したときにその名前で存在していた変数とは異なる場合があります。たとえば、次のスクリプトを考えてみましょう。

$ cat nameref.sh 
#!/bin/bash

var="from main"
declare -n ref=var               # a

echo "main:  \$ref='$ref'"

foo() {
    local var="from foo"
    bar
}

bar() {
    echo "bar(): \$ref='$ref'"   # b
}

foo

「a」行では最上位レベルがvar表示されますが(参照を保存すると考えることもあります)、$ref「b」行で使用すると表示される内容は参照varfoo()見つけたものです。

$ bash nameref3.sh 
main:  $ref='from main'
bar(): $ref='from foo'

関数で同じ結果を得ることもできます。

$ cat nameref4.sh

var=outer
foo() {
    declare -n ref=var
    echo "ref=$ref"
    local var=inner
    echo "ref=$ref"
}
        
foo

1つ目はecho外部コンテキストから値を取得し、2つ目はから割り当てたばかりの値を取得しますfoo()

Kusalanandaの答えで述べたように、Bash 5.0はループが発生したときに指定された変数の最上位インスタンスを取得します。ただし、これはまだ警告を発し、複数のネストされた関数がある場合は呼び出しスタックの途中で変数を参照できないため、あまり役に立ちません。


この点でKshは異なります。私はそれが正確に何であるかはわかりませんが、すぐにtypeset -n ref=var外部のコンテキストで変数を解析し、実際のIDを保存するようです。今回も地域変数一般的に言えばBashとは異なり、Kshでは、混合に名前参照がない場合でも、Kshを使用する複雑なプログラムに問題がある可能性があります。

関連情報