3

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.
2
  • 1
    I think FUNCNAME becomes a stack of function calls, in bash
    – Jeff Schaller
    Jun 10, 2016 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. Jun 10, 2016 at 15:49

2 Answers 2

7

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.

You must log in to answer this question.

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