They key problem of any 'named output variable' scheme where the caller can pass in the variable name (whether using eval
or declare -n
) is inadvertent aliasing, i.e. name clashes: From an encapsulation point of view, it's awful to not be able to add or rename a local variable in a function without checking ALL the function's callers first to make sure they're not wanting to pass that same name as the output parameter. (Or in the other direction, I don't want to have to read the source of the function I'm calling just to make sure the output parameter I intend to use is not a local in that function.)
The only way around that is to use a single dedicated output variable like REPLY
(as suggested by Evi1M4chine) or a convention like the one suggested by Ron Burk.
However, it's possible to have functions use a fixed output variable internally, and then add some sugar over the top to hide this fact from the caller, as I've done with the call
function in the following example. Consider this a proof of concept, but the key points are
REPLY
, and can also return an exit code as usualREPLY
(see the wrapper
example). The exit code of the function is passed through, so using them in e.g. an if
or while
or similar constructs works as expected.The reason this works is because the call
function itself has no locals and uses no variables other than REPLY
, avoiding any potential for name clashes. At the point where the caller-defined output variable name is assigned, we're effectively in the caller's scope (technically in the identical scope of the call
function), rather than in the scope of the function being called.
#!/bin/bash
function call() { # var=func [args ...]
REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?"
}
function greet() {
case "$1" in
us) REPLY="hello";;
nz) REPLY="kia ora";;
*) return 123;;
esac
}
function wrapper() {
call REPLY=greet "$@"
}
function main() {
local a b c d
call a=greet us
echo "a='$a' ($?)"
call b=greet nz
echo "b='$b' ($?)"
call c=greet de
echo "c='$c' ($?)"
call d=wrapper us
echo "d='$d' ($?)"
}
main
Output:
a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)