1

Is there a way for $val to be set in a () but not be seen by b ()?

set -u -e -o pipefail


a () {
  local +x val="myval"
  echo "in a: VAL= $val"
  b
}

b () {
  echo "in b: VAL= $val"
}

a

Produces:

in a: VAL= myval
in b: VAL= myval  # This should not happen.

I was hoping to use local/typeset options instead of the use of subshells to protect variables from being seen in other functions.

I've checked the manual (Functions section, typeset section) and there doesn't seem to be a way. However, I could have easily missed something.

John Militer
  • 773
  • 4
  • 14
  • 29
dgo.a
  • 769
  • 1
  • 8
  • 23
  • `val= b` is a workaround. – cuonglm Apr 04 '16 at 06:45
  • @cuonglm Do you mean setting the value of `$val` inside `b ()`? – dgo.a Apr 04 '16 at 06:53
  • No, change the last line of function a, `b` to `val= b`. – cuonglm Apr 04 '16 at 06:58
  • @cuonglm Thanks, but I've already considered that. In the real world, I would have to keep track of which variables for different functions. It's more "cognitive load" and for me that leads to more bugs and complicated code. – dgo.a Apr 04 '16 at 07:03
  • Then just do all the work in a subshell, then call `b` outside the subshell. – cuonglm Apr 04 '16 at 07:04
  • @cuonglm Thanks, but I've also considered that. It's not practical in some situations, like `IFS= ; for ... ; do b; done `. I also don't want to write function blocks like `( ....; .... ; .... ; ); b; ( .... ; ... ; ... ; ); c; ( ... )` unless there is a better way. Also, I may want to use the variable before and after `b ()`. – dgo.a Apr 04 '16 at 07:13

2 Answers2

0

Instead of restricting variables with subshells, you can run b as a new process. Then it won't have access to any of the caller's variables, except those you chose to export.

Move function to a new script

If you only need it for a single function, move that whole function into a separate script.

b:

#!/usr/bin/env mksh
set -u -e -o pipefail

echo "in b: VAL= $val"

Output:

in a: VAL= myval
./b[4]: val: parameter not set

Use a wrapper function

If you have lots of functions that need this, then you can move them all into a single file, and include a wrapper function to source-and-execute.

funcs:

#long options not passed on by -$-
set -o pipefail

#execute function without inheriting environment variables
function ni () {
  WHERE="${_%/*}"
  mksh -$- -c "source $WHERE/funcs ; $*"
}

function a () {
  local +x val="myval"
  echo "in a: VAL= $val"
  ni b
}

function b () {
  echo "in b: VAL= $val"
}

This "library" will need to be sourced from the main script:

#!/usr/bin/env mksh

set -e -u -o pipefail

WHERE="${_%/*}"
source $WHERE/funcs

a

This has the disadvantage that you lose the location (script and line number) of the error:

in a: VAL= myval
mksh: val: parameter not set
JigglyNaga
  • 7,706
  • 1
  • 21
  • 47
0

No.

However, if your call to b is a tail call and you “only” need to construct its arguments, there’s a way to do that which I’ve used in my “figure out the classpath then run the JAR” script.

function a {
    local val …

    # do something with local variables

    # construct arguments by setting the global variable _ to it
    set -A _ -- b arg1…
    # could even unset -f a here
}
a
"${_[@]}"

In my script I’m using exec "${_[@]}" as last line as I call an external command, I’m using the local variables technique to avoid accidentally polluting any exported environment variables.

Perhaps this helps?

Full disclosure: I’m the mksh developer.

mirabilos
  • 1,723
  • 15
  • 33