2

I have a function (=callee) that should effectively declare and assign a couple of variables in its caller. It should also be able to tell what it's caller's name is.

For now I achieve the former by return writing to a passed variable a string to be evaled by the caller.

 write_local_var_assignments variable_name; eval "$variable_name"

I imagine I can achieve the latter by either having the caller pass "$FUNCNAME" or by having the callee call the caller builtin and parse its output.

All these solution seem very clumsy so I have two questions:

  1. Is it possible for the callee to assign local variables into context of the caller, without the caller's cooperation?

I.e., can I compress:

 write_local_var_assignments variable_name; eval "$variable_name"

into just

 run_local_var_assignments

?

  1. Is there a better way to get the name of a function's caller? Getting the result directly without parsing or command substitution would be nice.
  • 1
    I think FUNCNAME becomes a stack of function calls, in bash – Jeff Schaller Jun 10 '16 at 15:41
  • @JeffSchaller Thanks. I just found it in the reference too. First I thought it was just a variable. I only just learned it's actually an array. – PSkocik Jun 10 '16 at 15:49
6

In bash (and ksh88, mksh, yash, dash, zsh), the local variable scoping is dynamic.

This code:

f() { a=2; echo "f: $a"; }
g() { local a=1; f; echo "g: $a"; }
a=0
g
echo "global: $a"

produces this output:

f: 2
g: 2
global: 0

That is f updates g's $a variable because it's called from g.

That's in contrast with the variables declared with typeset in functions declared with the ksh syntax (function f { ...; }) in ksh93 or variables declared with private in zsh where you'd get a:

f: 2
g: 1
global: 2

So in that case, you don't need to do anything.

As to know the name of the function calling you, in bash, you can use ${FUNCNAME[1]}.

The zsh equivalent is $funcstack[2]:

$ zsh -c 'f() { echo $funcstack[2]; }; g() { f; }; g'
g
$ bash -c 'f() { echo "${FUNCNAME[1]}"; }; g() { f; }; g'
g
2

If you don't mind using a command substitution, there is a function which will allow you to return self-eval'd strings to your caller, allowing you to call a run_local_var_assignments function of your creation thusly:

$(run_local_var_assignments)

The function looks like this:

emit () {
  local IFS=$'\n'
  printf 'eval eval %q' "$*"
}

It emits an eval statement much like your eval "$variable_name" above. Since it is the first thing in the resulting line, bash runs the emitted eval as a command.

The double eval and %q escaping done by the function are simply there because they're necessary to get newlines and other special characters out to the caller correctly.

Your function could then be written:

run_local_var_assignments () {
  local assignments=()
  assignments+=( 'local myvar1="my value1"' )
  assignments+=( 'local myvar2="my value2"' )
  emit "${assignments[@]}"
}

The assignments are written just as if they were source code, so things like spaces in the values require quotation marks as above.

Calling this function in a command substitution (as at the top of this answer) will create those local variables in the caller's scope, with those values.

You could also use a regular string or a heredoc instead of an array of statements, but I frequently find the array method useful to dynamically build up a block of statements, so I figured I'd show that even though it's slightly more complicated.

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.