6

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 it sources 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.

  • Could you invoke the source and checking parts to a subshell so they wouldn't actually affect the parent permanently? – Eric Renouf Jul 30 '15 at 18:34
  • If I did that, how could I keep the specific variables that I do want to persist from the child to the parent? – Hayden Schiff Jul 30 '15 at 18:37
  • @oxyguy3 If you're unsetting them I figured you didn't really need them to be persistent, perhaps there are others though – Eric Renouf Jul 30 '15 at 18:40
  • @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. – Hayden Schiff Jul 30 '15 at 18:48
  • 2
    This 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. – goldilocks Jul 30 '15 at 19:02
5

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
  • 1
    The 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___() – yolenoyer Mar 3 '18 at 22:43
  • To clean up the function itself, you can use unset -f func just after calling it. – jwd Jul 10 at 20:50
-1

I believe the syntax for making a variable local in bash is:

local variable_name=

I know this works for functions and am not sure how it works with multiple scripts.

  • 1
    It seems that it can only be used for functions; I get an error: local: can only be used in a function – Hayden Schiff Jul 30 '15 at 18:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.