Resistor color exercise

I am attempting the resistor color exercise.

I don’t understand why I can’t use "${!colors[@]}" outside of the function. It works from within the function, but not outside of it in the case statement. As I understand, a variable in a function is global unless marked as local.

Any suggestions are appreciated.

#!/bin/bash

r_colors() {        

declare -A colors

    colors[black]="0"
    colors[brown]="1"
    colors[red]="2"
    colors[orange]="3"
    colors[yellow]="4"
    colors[green]="5"
    colors[blue]="6"
    colors[violet]="7"
    colors[grey]="8"
    colors[white]="9"

#set -x
echo "Please enter a color: "
read -r user_color_input

for (( i=0; i <= ${#colors[@]}; i++ ))

 do
                                                     
     if [[ $user_color_input == ${!colors[$i]} ]]  
    
        then    
            break    
     fi  

 done
        echo "${colors[$user_color_input]}"
        echo "${!colors[@]}" #<-test

} #<-end of r_colors


##########################################################################################
clear
echo "Please enter 1 for color value, or enter 2 for a list of colors: "

        read -r user_menu_input

        case "$user_menu_input" in

                1) r_colors
                ;;  

                2) echo "${!colors[@]}" #<-this prints nothing
                ;;  

                *) echo "You made an invalid choice, please try again" 
                ;;  

        esac
1 Like

declare inside a function by default makes local variables. As your code clearly illustrates :slight_smile:

Indeed, to create a global variable from inside of a function, you need to use the -g flag:

function foo {
    declare -g var=value
}

declare -p var
foo
declare -p var

outputs

bash: declare: var: not found
declare -- var="value"

It still doesn’t work.

Does it matter that it’s an Associative array?
This is what I’ve done:

declare -gA colors

The rest of the code is the same as above.

It works perfectly fine for me. Are you sure it doesn’t work? How are you checking it?

$ foo() { declare -gA colors; }

$ declare -p colors
bash: declare: colors: not found

$ foo

$ declare -p colors
declare -A colors

@duvel I think it would be helpful at this point if you were to share your full code.

Oh I see now. Option 2 does nothing because you haven’t called r_colors, which populates the array. You will have to restructure your code.

Also, do read the track docs about how to test. I’m on my phone right now so I don’t have a link for you

As far as I understand, there is no way to call a particular element of a regular function, outside of that function. It’s all or nothing.

But would that not be the case if that element was global? In this case, an Associative array?

I am just trying to display the result of "${!colors[@]}" as option 2 of the case, but that value resides inside the function.

I could do away with the function, and put the full code in the case statement, but that would be a mess.

Thoughts?

This is what I have:

#!/bin/bash

r_colors() {        #<-resistor colors

 declare -gA colors

    colors[black]="0"
    colors[brown]="1"
    colors[red]="2"
    colors[orange]="3"
    colors[yellow]="4"
    colors[green]="5"
    colors[blue]="6"
    colors[violet]="7"
    colors[grey]="8"
    colors[white]="9"

 #set -x
 
 echo "Please enter a color: "
 read -r user_color_input

 for (( i=0; i <= ${#colors[@]}; i++ ))

  do  
    
     if [[ $user_color_input == "${!colors[$i]}" ]]  
                 
        then                                  
            break        
     fi  

  done
        echo "${colors[$user_color_input]}"
        echo "${!colors[@]}" #<a test

} #<-end r_colors

##########################################################################################
clear

echo "Please enter 1 for color value, or enter 2 for a list of colors: "

     read -r user_menu_input

        case "$user_menu_input" in

                1) r_colors
                ;;  

                2) echo "${!colors[@]}"                 
                ;;  

                *) echo "You made an invalid choice, please try again" 
                ;;  

        esac

Functions don’t have “elements”. They contain code and define variables. Neither of which can be called.

Functions can define global variables which can then be accessed outside that function.

Variable values reside in variables. Those variables can be scoped to a function or scoped globally.

You really, really should take the time to learn the basics of shell coding so you understand the basics. It would make your life a lot easier, and it would make it easier for us to help you if you understand the basics first.

» foo () { declare -gA v; v[name]=bob; }
» unset v
» foo
» declare -p v
declare -A v=([name]="bob" )

» echo "${v[name]}"
bob
» unset colors
» r_colors() { declare -gA colors; colors[black]="0"; colors[brown]="1"; }
» r_colors
» echo "${!colors[@]}"
black brown

And you can only access the variables after you invoke the function. Bash can’t reach into the function and pull out global variables.

Correct

Why would this be an exception to “it’s all or nothing”?

Refactor. You need the array in multiple cases, so don’t define it so that it only can be used in the first case. Pull the definition out of the function and just make it a variable in the global scope.

Maybe I’m misunderstanding what is meant by global. I’m declaring the associative array, colors, to be global from within the function.
I thought global meant that it would be accessible outside of the function.

So why doesn’t it work when I call it from the case statement?

Is there a way to invoke the function without it running through its own code? I only want to display "${!colors[@]}" from it.

That’s correct, as demonstrated with my above code block.

Can you demonstrate what doesn’t work? The code you run and what happens when you run it? “Doesn’t work” is not specific. Notice how I shared both code and the output, allowing you to reproduce what I did and check the outcomes match.

“Invoke” and “run” are the same thing. A function will only be invoked or run if something else (code or the user on the terminal) runs it. Functions don’t run themselves.

Here is a simplified version of the code to demonstrate what is happening:

#!/bin/bash

r_colors() {     

 declare -gA colors

    colors[black]="0"
    colors[brown]="1"
    colors[red]="2"
    colors[orange]="3"
    colors[yellow]="4"
    colors[green]="5"
    colors[blue]="6"
    colors[violet]="7"
    colors[grey]="8"
    colors[white]="9"

    echo "${!colors[@]}"

} #<-end r_colors

##########################################################################

echo "Enter 1 to run the function r_colors, 2 for a list of colors: "

     read -r user_menu_input

        case "$user_menu_input" in

                1) r_colors
                ;;  

                2) echo "${!colors[@]}"
                ;;  

                *) echo "Invalid choice, please try again"
                ;;  

        esac

Choice 1) of the case runs the function, and prints: black yellow brown violet orange blue grey red white green perfectly from within the function.

Choice 2) of the case tries to print the same thing, but from outside of the function. It prints nothing.

Because colors is declared as global, I would think that it could be called from outside of the function; from the case statement.

You need to run the function before you access the variable. Try getting rid of the read and case and just calling the function then printing the variable.

Note, a simple reproduction shouldn’t require human inputs. If it depends on human inputs, you’d want to show those inputs and the outputs. But ideally it would just run.

I know that I could call the function in its simplified form and print "${!colors[@]}" , because that’s all the simplified version does.

But is there a way to call the full function to only print "${!colors[@]}" without it running through its full code? That’s all I need option 2 of the case to do:

#!/bin/bash

 r_colors() {        #<-resistor colors

 declare -gA colors

    colors[black]="0"
    colors[brown]="1"
    colors[red]="2"
    colors[orange]="3"
    colors[yellow]="4"
    colors[green]="5"
    colors[blue]="6"
    colors[violet]="7"
    colors[grey]="8"
    colors[white]="9"

 #set -x

 echo "Please enter a color: "
 read -r user_color_input

 for (( i=0; i <= ${#colors[@]}; i++ ))

  do

     if [[ $user_color_input == "${!colors[$i]}" ]]

        then
            break
     fi

  done
        echo "${colors[$user_color_input]}"

} #<-end r_colors

The criteria for the exercise is that 1) a user can look up the value for a particular color, and 2) simply list the different colors. That is why the for loop and the read/case statement is in there.

If there’s not a way, I would have to pull the array out of the function, making it naturally global.

No. You can’t run part of a function. A function runs until it hits a return, exit or the end of the end of the code. You can’t have it magically run just part. You could add an if something; then return; fi to make it run part of the code.

The criteria for the exercise is also not to read from STDIN, which read violates :slight_smile: