Best way to make variables local in a source'd bash script?
Wrap the whole script you want to source into a function, add local before the declarations you want to only use in the function, and call the function at the end of the script.
func () {
local name="My awesome job"
nowTime=`expr $(date +%s) `
lastActiveTime=`expr $(date +%s -r ~/blah.log)`
local secsSinceActive=`expr $nowTime - $lastActiveTime`
currentRaw=$(cat ~/blah.log | grep "Progress" | tail -n 1)
if [ -z "$currentRaw" ]; then
local statusText="Not running"
else
local statusText="In progress"
fi
}
func
Hayden Schiff
Hey there! I do software and data things. You can find info about my other projects on my personal website or my GitHub, or read my incessant tweets over on my Twitter.
Updated on September 18, 2022Comments
-
Hayden Schiff over 1 year
I have a bash script that generates a report on the progress of some long-running jobs on the machine. Basically, this parent script loops through a list of child scripts (calling them all with
source
). The child scripts are expected to set a couple of specific variables, which the parent script will then make use of.Today, I discovered a bug where, a variable set by the first child script accidentally got used by the second child script, causing incorrect output. Is there a clean way to prevent these types of bugs from happening?
Basically, when I
source
a child script, there are a couple of specific variables that I want to persist back to the parent script. My parent script resets these specific variables before itsource
s each new child script, so there are no issues with them. However, some child scripts may have additional arbitrary variables that it uses locally that should not persist back to the parent script.Obviously I could manually unset each of these at the end of the child script, but these seems prone to error if I forget one. Is there a more proper way of sourcing a script, and having only certain variables persist to the script that called
source
?edit: For sake of clarity, here's a sort of dumbed down version of my parent script:
echo "<html><body><h1>My jobs</h1>" FILES=~/confs/*.sh for f in $FILES; do # reset variables name="Unnamed job" secsSinceActive="Unknown" statusText="Unknown" # run the script that checks on the job source "$f" # print bit of report echo "<h3>$name</h3>" echo "<p>Last active: $secsSinceActive seconds ago</p>" echo "<p>Status: $statusText</p>" echo "</body></html>"
And here's what one of the child scripts might look like:
name="My awesome job" nowTime=`expr $(date +%s) ` lastActiveTime=`expr $(date +%s -r ~/blah.log)` secsSinceActive=`expr $nowTime - $lastActiveTime` currentRaw=$(cat ~/blah.log | grep "Progress" | tail -n 1) if [ -z "$currentRaw" ]; then statusText="Not running" else statusText="In progress" fi
The variables $name, $secsSinceActive, and $statusText need to persist back to the parent script, but all the other variables should disappear when the child script terminates.
-
Eric Renouf almost 9 yearsCould you invoke the
source
and checking parts to a subshell so they wouldn't actually affect the parent permanently? -
Hayden Schiff almost 9 years@EricRenouf I added some example code for clarity; I need some specific variables to persist, but any variable outside of my list of specifically approved variables needs to die.
-
goldilocks almost 9 yearsThis is a quintessential pitfall of using global variables in programming. You should use a more robust mechanism for this, such as parsing values from the output of each subprocess or using temp files to store them.
-
-
Hayden Schiff almost 9 yearsIt seems that it can only be used for functions; I get an error:
local: can only be used in a function
-
yolenoyer about 6 yearsThe best solution imo, but for those who read this answer, keep in mind that the above
func()
function will however stay present into the parent scope, and pollute it; to avoid conflicting names, the best solution I know is to give to it an ugly name, something like___this_main___()
-
jwd almost 5 yearsTo clean up the function itself, you can use
unset -f func
just after calling it.