1

I have a shell function, which accesses and touches a global array, which is basically a cache. It returns the value by echoing it:

declare -A cache

function get_value() {
    if [ ${cache[$1]+exists} ]; then
        echo ${cache[$1]}
    else
        value=$(create_value $1) # want to cache this result
        cache[$1]="${value}"
        echo $value
    fi
}

If I call it in the standard way

myValue=$( get_value "foo" )

it does not work because the cache[] update in the function happens in a subshell (the $( ... )) and is lost upon returning to the script aka parent shell.

The only thing I can think of is using a global variable (result) for the return value, but of course that's not so great in terms of structured programming:

declare -A cache

function get_value() {
    if [ ${cache[$1]+exists} ]; then
        result=${cache[$1]}
    else
        value=$(create_value $1) # want to cache this result
        cache[$1]="${value}"
        result=$value
    fi
}

get_value "foo"
myValue=$result

Is there a better alternative?

Using Bash 4.2.45.

0

1 Answer 1

2

You can pass the variable name to which you want to assign the result as a parameter to the function, and use printf -v to perform the assignment :

declare -A cache

function get_value() {
    if [ ${cache[$1]+exists} ]; then
        printf -v "$2" "${cache[$1]}"
    else
        local value=$(create_value "$1") # want to cache this result
        cache[$1]="$value"
        printf -v "$2" "$value"
    fi
}

get_value "foo" my_value

If you are going to control variable scope, you might as well make your value variable local and, why not, make some kind of main() function to keep all variables local (even your cache).

Sign up to request clarification or add additional context in comments.

6 Comments

create_value "$1", it should be. Other than that, my objections are all subject to opinion/debate (ie. using the needlessly-and-uselessly-nonportable function keyword).
BTW, you can use declare -g -A cache to declare your cache inside the function but still have it be global. I tend to prefix with the function name to provide a namespace in such cases: declare -g -A get_value__cache or such.
I have used the -g option a couple of times where it was the only way to achieve the desired result, but usually I will keep variables local if I can (i.e. declaring them in some kind of main() function). Using the -g option, one would probably first want to check that it has not been assigned yet to avoid, for instance, destroying the cache each time the function using it is called (or else it would not be much of a cache).
declare -g doesn't actually clear the value if one already exists of the correct type. If you did declare -g -A get_value__cache=( ) then that would, but you'll notice there was no = in my suggestion above.
@CharlesDuffy I always use set -u, and I think declaring empty arrays is necessary in that case to avoid additional complexity (e.g. to check if the variable is assigned before checking how many elements it contains), but I see that without this option it could work if you are sure there is no collision with the name of another global variable.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.