1

Context:

We have several pieces of our infrastructure that are managed by large sets of Bash scripts that interact with each other via the source command, including (and frequently chaining includes) of files that are created compliant with a standard Bash template we use. I know that this is a situation that should probably never have been allowed, but it's what we have.

The template basically looks like this:

set_params() {
    #for parameters in a file that need to be accessed by other methods 
    #in that file and have the same value from initialization of that 
    #file to its conclusion:
    global_param1=value 

    #left blank for variables that are going to be used by other methods 
    #in the file, but don't have a static value assigned immediately:
    global_param2= 
}

main_internals() {
    #user-created code goes here.
}

main() {
    set_params
    #generic setup stuff/traps go here
    main_internals arg arg arg
    #generic teardown stuff goes here
}

Using this structure, we have files include other files via the source command and then call the included files main methods, which wraps and modularizes most operations well enough.

Problem:

Some of the thorniest problems with this infrastructure arise when a new module is added to the codebase that uses a global variable name that is used somewhere else, unrelatedly, in the same sourced chain/set of files. I.e if file1.sh has a variable called myfile which is uses for certain things, then sources file2.sh, and then does some more stuff with myfile, and the person writing file2.sh doesn't know that (in many cases they can't be expected to--there are a lot of files chained together), they might put a non-local variable called myfile in file2.sh, changing the value in the variable with the same name in file1.sh

Question:

Assuming that global variable name conflicts will arise, and that localing everything can't completely solve the problem, is there some way to programmatically unset all variables that have been set in the global scope during the execution of a particular function or invocations below that? Is there a way to unset them without unsetting other variables with the same names that are held by files that source the script in question?

The answer might very well be "no", but after looking around and not finding much other than "keep track of variable names and unset anything after you're done using it" (which will inevitably lead to a costly mistake), I figured I'd ask.

Phrased another way: is there a way to make/hack something that works like a third scope in Bash? Something between "local to a function" and "visible to everything running in this file and any files sourced by this one"?

1 Answer 1

3

The following is untested.

You can save a lot of your variables like this:

unset __var __vars
saveIFS=$IFS
IFS=$'\n'
__vars=($(declare -p))
IFS=$saveIFS

or save them based on a common prefix by changing the next to last line above to:

__vars=($(declare -p "${!foo@}"))

Then you can unset the ones you need to:

unset foo bar baz

or unset them based on a common prefix:

unset "${!foo@}"

To restore the variables:

for __var in "${__vars[@]}"
do
    $i
done

Beware that

  • variables with embedded newlines will do the wrong thing
  • values with whitespace will do the wrong thing
  • if the matching prefix parameter expansion returns an empty result, the declare -p command will return all variables.

Another technique that's more selective might be that you know specifically which variables are used in the current function so you can selectively save and restore them:

# save
for var in foo bar baz
do
    names+=($var)
    values+=("${!var}")
done

# restore
for index in "${!names[@]}"
do
    declare "${names[index]}"="${values[index]}"
done

Using variable names instead of "var", "index", "names" and "values" that are unlikely to collide with others. Use export instead of declare inside functions since declare forces variables to be local, but then the variables will be exported which may or may not have undesirable consequences.

Recommendation: replace the mess, use fewer globals or use a different language.

Otherwise, experiment with what I've outlined above and see if you can make any of it work with the code you have.

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

1 Comment

This seems like a reasonable solution to a problem for which I've come up with many less reasonable solutions over the years. Thank you. :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.