Unix & Linux
bash array associative-array
Updated Sun, 12 Jun 2022 16:55:47 GMT

Assoc. Array not being redeclared?


I have a program that acts like a menu. It has an associative array called config such as:

declare -A config=( [h]="?" [c]="?" [x]="?" [l]="?" [t]="?" [n]="?" )

In the main loop there's a check to see if all of the values have been configured, like:

if [ "${config[h]}" == "Y" ] && [ "${config[c]}" == "Y" ] && [ "${config[l]}" == "Y" ] && [ "${config[x]}" == "Y" ] && [ "${config[t]}" == "Y" ] && [ "${config[n]}" == "Y" ];

Now, when I finish one run, I unset and redeclare the array.

unset config; declare -A config=( [h]="?" [c]="?" [x]="?" [l]="?" [t]="?" [n]="?" )

However, the array doesn't seem to be re-declared properly. This is because when I configure just one of the values, [C]=Y for example, the if statement passes. I know for sure it does because the body of the if statement changes some of the text color. I believe it's because the rest of the indices aren't actually set="?", so the if statement is reduced to just [ "${config[c]}" == "Y"] which is be true. I know this since when I echo $config[@] inside the body of the if statement, sure enough I only see one "Y" instead of five. How do I get the array to redeclare properly?

EDIT
Thank you for your attention;

  1. Values are set to Y after some user input. This part I'm very confident about and thus omitted from my question. They all follow this format:

     read ch
            if [ $ch == "Hosts" ]; then
                    while true; do
                            nano listHosts
                            echo -en "Commit this list of Hostnames? [Y|N to re-edit]: "
                            read yn
                            if [ $yn == "Y" ] || [ $yn == "y" ] || [ $yn == "yes" ]; then
                                    break
                            elif [ $yn == "N" ] || [ $yn == "n" ] || [ $yn == "no" ]; then  
                                    continue
                            fi      
                    done
                    config[h]="Y"
    
  2. Regarding Kusalanda's comment, I checked for incorrect casing but it's consistently lower-case 'c'. As well, yes, I meant ${config[@]}.

  3. Declare -p sheds some light: declare -a config='([0]="Y")'

How come -p says I used lowercase a? The calls are in this order:

 unset config
 declare -A config=( [h]="?" [c]="?" [x]="?" [l]="?" [t]="?" [n]="?" )   

Then I set $ch="Commands" via read;

 elif [ $ch == "Commands" ]; then  
                while true; do
                        nano iSet
                        echo -en "Commit this list of commands? [Y|N to re-edit]: "             
                        read yn                        
                        if [ $yn == "Y" ] || [ $yn == "y" ] || [ $yn == "yes" ]; then           
                                break
                        elif [ $yn == "N" ] || [ $yn == "n" ] || [ $yn == "no" ]; then                  
                                continue        
                        fi      
                done        
                config[c]="Y"
                declare -p config

I tried to recreate in a smaller script as per Bodo's suggestion:

dec() {
        declare -A config=( [h]="?", [c]="?" )
}
test() {
        declare -p config
        if [ "${config[h]}" == "Y" ] && [ "${config[c]}" == "Y" ]; then 
                echo "Yup"
        fi
}
dec
config[h]="Y"; config[c]="Y"
unset config
dec
config[h]="Y" 
test

And just like in my other script, the if resolves to true: declare -a config='([0]="Y")' Yup




Solution

You have to declare the array as global in your functions using declare -g .... Otherwise the array will be a local variable in the function. See https://unix.stackexchange.com/a/136721/330217

See this modified script with some debug output

#! /bin/bash
# set -x
dec() {
        # declare -A config=( [h]="?", [c]="?" )
        declare -gA config=( [h]="?" [c]="?" )
        echo dec: ${config[*]}
}
test() {
        declare -p config
        if [ "${config[h]}" == "Y" ] && [ "${config[c]}" == "Y" ]; then
                echo "Yup"
        else
                echo "No"
        fi
        echo test: ${config[*]}
}
dec
echo 1: ${config[*]}
config[h]="Y"; config[c]="Y"
echo 2: ${config[*]}
test
unset config
dec
echo 3: ${config[*]}
config[h]="Y"
echo 4: ${config[*]}
test

The output is

$ ./script
dec: ? ?
1: ? ?
2: Y Y
declare -A config=([c]="Y" [h]="Y" )
Yup
test: Y Y
dec: ? ?
3: ? ?
4: ? Y
declare -A config=([c]="?" [h]="Y" )
No
test: ? Y

When I uncomment your line and comment my modified line, the output is

$ ./script
dec: ? ?,
1:
2: Y
declare -a config=([0]="Y")
Yup
test: Y
dec: ? ?,
3:
4: Y
declare -a config=([0]="Y")
Yup
test: Y

Copied from @ilkkachu's comment:

And of course, after the local declaration of the associative array falls out of scope, the assignment config[h]="Y" creates a regular array, where the index is interpreted in an arithmetic context, where h (recursively) expands the value of the variable h, which probably isn't set and you get the empty string that evaluates to zero, so it's config[0] that gets set. And there's no error message since set -u isn't in effect.

This can be seen in the output declare -a config=([0]="Y") from the original script.





Comments (3)

  • +0 – Using the -g switch fixed this problem! Thank you. — Jul 25, 2019 at 15:05  
  • +2 – And of course, after the local declaration of the associative array falls out of scope, the assignment config[h]="Y" creates a regular array, where the index is interpreted in an arithmetic context, where h (recursively) expands the value of the variable h, which probably isn't set and you get the empty string that evaluates to zero, so it's config[0] that gets set. And there's no error message since set -u isn't in effect. — Jul 25, 2019 at 15:08  
  • +0 – @ilkkachu thank you for the in-depth insight! — Jul 25, 2019 at 16:08  


External Links

External links referenced by this document: