There's some interesting shenanigans going on with references:
#!/bin/bash
read -r -d '' payload << EOF
apple: vinegar
orange: juice
EOF
populate() {
declare -n _aa=$1
declare data="this is not even an array"
while IFS=": " read -r key value; do
_aa["$key"]="$value"
done <<< "$payload"
echo "~~~ populate about to finish, data now: ${_aa[*]@K}"
}
process() {
declare -A data=( ["mango"]="lassi" )
populate data
echo "~~~ process about to terminate, data now: ${data[*]@K}"
}
process
Note that there is two variables named data - one within process function and another in populate - they have nothing to do with each other, they are different kinds and both local to each function. But the code as above does not work as intended:
~~~ populate about to finish, data now: 0 "juice"
~~~ process about to terminate, data now: mango "lassi"
The variable reference inside populate apparently references data variable inside, not outside. Once left, the other variable is intact. All it takes is to use different name for one of the data variables:
~~~ populate about to finish, data now: orange "juice" apple "vinegar" mango "lassi"
~~~ process about to terminate, data now: orange "juice" apple "vinegar" mango "lassi"
Now I am aware of "circular reference" issue when it's a problem to call the referenced variable the same as the one inside the function declaring reference. But how can one reasonably prevent the above? One has to predict each and every variable name despite they are local to each function in this case? Is there any explanation for this? Is declare -n just repackaged old-fashioned indirection?
NOTE I found this in the Bash reference:
Local variables "shadow" variables with the same name declared at previous scopes. For instance, a local variable declared in a function hides a global variable of the same name: references and assignments refer to the local variable, leaving the global variable unmodified. When the function returns, the global variable is once again visible.
The above experience gives it a whole new meaning - references refer to local variable no matter what. There is very little mentioned that I could find how references work when both sides were local.
I have since found Greg's wiki take on references:
if you have declare -n ref=$1 and try to use $ref, the shell will look for whatever variable of that name is visible by the current function. There is no way to override this. You cannot say "this nameref points to the variable foo in the caller's scope" unambiguously.
It's not exactly true either. Clearly, the reference works across 2 functions where each had that variable local. So it's not variable of a name "visible by the current function".
My concern is a reference may work till some point in the function and then it becomes reference to something else as it locally shadows it with no indication of it.
Then there's also the problem with bash's name references relating to the more well known bash: warning: v: circular name reference:
You can avoid the circularity by using declare only if the names do not clash
$ foo() { if [[ $1 != v ]]; then declare -n v=$1; fi; echo $v; }
$ bar() { if [[ $1 != v ]]; then declare -n v=$1; fi; foo v; }
$ v="xyz"
$ bar v
xyz
The above is fairly impractical, so to say, and cannot be applied to the case here - where the reference comes from another local variable and even worse, gets lost later within the function.
I could not find any comprehensive explanation of Bash scoping, it's just examples with warnings what will break. Is nameref actually any different than ${!indirection}? And how come the ref is even visible to the other function here, where it was local in the caller?
populatefunction with parameterdatathe nameref_aarefers to the variable nameddatain the caller's scope (theprocessfunction) only between the first and seconddeclarestatements. The seconddeclarestatement creates a local variable with the same name that hides the other one. So, after the seconddeclarestatement_aarefers to this new local variable. What other behavior do you expect?datais one level up until you declare the local variabledata. Starting from that the scope is local.foo() { echo "a=$a"; }; bar() { declare a="a"; foo; }; bar. This printsa=aand demonstrates that variablea, which is local to functionbar, is shared with functionfoothatbarcalls.