Oh dear. I've been trying to get people to not use this feature for a while.
One thing that has bitten me in the past is that, if you declare your associative arrays within a function, that associative array is ALWAYS global.
Even if you declare it with `local -A` it will still be global.
Despite that, you cannot pass an associative array to a function by value. I say "by value" because while you can't call `foo ${associative_array}` and pick it up in foo with `local arg=$1, you can pass it "by reference" with `foo associative_array` and pick it up in foo with `local -n arg=$1`, but if you give the passed in dereferenced variable a name that is already used in the global scope, it will blow up, eg `local -n associative_array=$1`.
As a general rule for myself when writing bash, if I think one of my colleagues who has an passable knowledge of bash will need to get man pages out to figure out what my code is doing, the bash foo is too strong and it needs to be dumbed down or re-written in another language. Associative arrays almost always hit this bar.
I'm not seeing this local scope leak with bash 5.2.15. The below script works as I'd expect:
#!/bin/bash
declare -A map1=([x]=2)
echo "1. Global scope map1[x]: ${map1[x]}"
func1() {
echo " * Enter func1"
local -A map1
map1[x]=3
echo " Local scope map1[x]: ${map1[x]}"
}
func1
echo "2. Global scope map1[x]: ${map1[x]}"
outputting
1. Global scope map1[x]: 2
* Enter func1
Local scope map1[x]: 3
2. Global scope map1[x]: 2
The local scope leak seems to only happen when you drop down the call stack. See below how I can call func2 from the top level and it's fine, but if I call it from within func1, it will leak.
#!/bin/bash
declare -A map1=([x]=2)
echo "1. Global scope map1[x]: ${map1[x]}"
func1() {
echo " * Enter func1"
local -A map1
map1[x]=3
echo " Local scope map1[x]: ${map1[x]}"
func2
}
func2() {
echo " * Enter func2"
echo " Local scope map1[x]: ${map1[x]}"
}
func1
func2
echo "2. Global scope map1[x]: ${map1[x]}"
outputing:
1. Global scope map1[x]: 2
* Enter func1
Local scope map1[x]: 3
* Enter func2
Local scope map1[x]: 3
* Enter func2
Local scope map1[x]: 2
2. Global scope map1[x]: 2
UPDATE: I did a bit of exploration and it turns out ANY variable declared `local` is in the scope of a function lower down in the call stack. But if you declare a variable as `local` in a called function that shadows the name of a variable in a callee function, it will shadow the callee's name and reset the variable back to the vallee's value when the function returns. I have been writing bash for years and did not realise this is the case. It is even described in the man page:
When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children.
Thank you. You have taught me two things today. One is a bash feature I did not know existed. The second is a new reason to avoid writing complex bash.
One thing that has bitten me in the past is that, if you declare your associative arrays within a function, that associative array is ALWAYS global. Even if you declare it with `local -A` it will still be global. Despite that, you cannot pass an associative array to a function by value. I say "by value" because while you can't call `foo ${associative_array}` and pick it up in foo with `local arg=$1, you can pass it "by reference" with `foo associative_array` and pick it up in foo with `local -n arg=$1`, but if you give the passed in dereferenced variable a name that is already used in the global scope, it will blow up, eg `local -n associative_array=$1`.
As a general rule for myself when writing bash, if I think one of my colleagues who has an passable knowledge of bash will need to get man pages out to figure out what my code is doing, the bash foo is too strong and it needs to be dumbed down or re-written in another language. Associative arrays almost always hit this bar.