Thanks, but I think I found a very nice, less complicated solution, and
as I now understand from the investigation of the intricacies of `unset'
elsewhere in this thread, it is perfect legitimate.  A speed comparison
between all different solutions/workarounds would be interesting though?

My solution is to explicitly define the return variables "local" in the
callee.  Then the callee can unset them again from a called helper
function, effectively making the variable appear down the call-stack to
the caller.

To summarize: I'm seeing this technique of 'passing by reference'
popping up on the web:

    blackbox() { eval $1=bar; }
    f() { local b; blackbox b; declare -p b; }
    f  # Looks ok: b=bar

but having the caveats of an unsafe `eval' and this `local' conflict:

    blackbox() { local b; eval $1=bar; }
    f() { local b; blackbox b; declare -p b; }
    f  # Error: b=, because blackbox() has a `local b' declared

The solution is to have a helper function `_return' like this:

    _return() { unset -v "$1" && eval $1=\"\$2\"; }
    blackbox() { local b && _return b bar; }
    f() { local b; blackbox b; declare -p b; }
    f  # Ok: b=bar

Or a more elaborate example, which is also capable of returning arrays
by reference:

    a=A; unset -v b  # Initialize globals for test case

    # Return variables by reference
    # Param: $1  Variable name to return value to
    # Param: $*  Value(s) to return.  If multiple values, an array is
    #            returned, otherwise a single value is returned.
    _return_by_ref() {
        if unset -v "$1"; then           # Unset & validate varname
            if [ $# -eq 2 ]; then
                eval $1=\"\$2\"          # Return single value
            else
                eval $1=\(\"\${@:2}\"\)  # Return array
            fi
        fi
    }

    # Example public library function
    # Param: $1 Variable to return value to
    # Param: $2 Variable to return array to
    blackbox_by_ref() {
        local a b c d e f=(foo "bar \"cee" $'cee\n  dee') # etc. 
        # ...
        # non-obfuscated variables used here :-)
        # ...
        # return values
        local "$1" && _return_by_ref $1 "bar"
        local "$2" && _return_by_ref $2 "$...@]}"
    }

    client() {
        local a=1 b=1
        blackbox_by_ref a b; declare -p a b     # Ok: a=bar, b...@]
        a='ls /;true'; blackbox_by_ref "$a" b   # No oops
    }

    client          # Ok: a=bar, b...@] and error because of oops test
    declare -p a b  # Ok: Globals are unchanged (a=A, b=nil)

It seems to work all right on bash-2.05b, bash-3.0, bash-3.2, bash-4.0,
bash-4.1.

The `eval' could be replace by `printf -v', but this requires bash >
3.1-alpha1 and the eval is relative safe because the varname is checked
by `unset'?

So now we can stop condemning people who're passing variables by
reference and write even more beautiful bash code! ...

...until we find another caveat ;-) am I overlooking something?


Freddy Vulto
http://fvue.nl/wiki/Bash:_passing_variables_by_reference


Reply via email to