5

I want to implement a script foo.sh that culminates in the an execution of a command bar (with some arguments). The command bar modifies the current shell environment, which means that foo.sh must be sourced1. (BTW, the implementation of bar is entirely outside my control.)

The purpose of most of the code in foo.sh is to compute the arguments that will be passed to bar.

In order to keep foo.sh reasonably legible/maintainable, I find that I must define many auxiliary variables. Unfortunately, this sets the stage for

  1. namespace collisions (i.e. clobbering existing parameters)
  2. namespace pollution (i.e. cluttering the environment with superfluous parameters)

One possible way to avoid, or at least mitigate, these problems is to put most of foo.sh's code into a function (with all its variables declared as local) that echoes a suitable string of code to be eval'ed within the calling scope; e.g.

{
  __messy_calculation () {

      local x y z ...
      local bar_arg_1 bar_arg_2 ...
      ...
      echo "bar $bar_arg_1 bar_arg_2 ..."

  }

  eval "$( __messy_calculation )"

} always {

  unfunction __messy_calculation

}

This takes care of the namespace pollution problem, and reduces the namespace collision problem to the one for the name of the function. (I'm not a big fan of using eval, though.)

I figure that this situation is generic enough that there already exist standard ways to address it. If so, please let me know.


1In case my reasoning here is flawed, let me just add that if I put all the code, including the call to bar, into a function, when I execute this function on the command line the current environment remains unaffected, which renders the function useless. In contrast, if I source the line that executes the call to bar, the current environment is modified as expected.

2
  • 2
    What makes you think you need to use eval? Just call the function.
    – psusi
    Nov 12, 2015 at 0:21
  • 1
    You don't even need to define __messy_calcuation; zsh supports anonymous functions: function { local x y z; local bar_arg_1 ...; ...; bar $bar_arg_1 $bar_arg_2; }
    – chepner
    Nov 12, 2015 at 14:27

2 Answers 2

4

For variables it is possible to use an anonymous function (as noted in the comment above). That means you can can define as many variables as you want. The ones you want to hide from the caller you prefix with local, the ones you want to communicate to the caller you just define normally:

function {
  local my_var=foo
  your_var=bar
  : ...
}

But for functions that will not work as they can not be declared local. I had this problem in my zshrc and decided to prefix all names of my "local" functions with a short string (like you do namespaces in C) and to do a wildcard unfunction at the end:

function foo-do-something () {
  : ...
}
function foo-do-some-more-stuff () {
  : ...
}
function foo-main () {
  local my_var=oof
  you_var=rab
  foo-do-something for bar
  foo-do-some-more-stuff with baz
}
foo-main
unfunction -m 'foo-*'

This is not an optimal solution in my opinion (I would prefer local functions) but it works for me.

1
  • For Bash unset -f <name> will do the trick. If namespaced functions are used then this line will unset all of them unset -f $(compgen -A function <prefix>). Jan 28, 2022 at 12:01
3

The solution is using subshells, nested functions and unset for the main func:

my_app_main()
(
  unset -f my_app_main

  a()
  (
    x=1
    echo "a.x is $x"
  )

  b()
  (
    x=2
    echo "b.x is $x"
  )

  c()
  (
    echo "c.x is $x (empty)"
  )

  a
  b
  c
)

my_app_main "$@"

echo "$x (empty - x is gone)"

# all gone
type my_app_main a b c
2
  • Why do a, b, and c vanish despite not being declared local?
    – Hatefiend
    Jan 26, 2023 at 10:36
  • Because my_app_main body is enclosed with () instead of {}.
    – awvalenti
    Jan 27, 2023 at 14:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .