In Bash 3.2.0(1)-release, "local" displays local variables that do and do not have values. In Bash 4.0.33(1)-release and 4.1.0(1)-release only those with values are printed. Oops.
f () { local var1 var2=abc var3=; local; }; f Bash 3: var1= var2=abc var3= Bash 4: var2=abc var3= So it looks like the only way is to use "local var=" instead of "local var". I can understand the desire to have blackbox_var_sane "$3" called near the eval, but it seems to me to make sense to have it fail early rather than waste "# Lots of complicated library code here" first. This also applies to the placement in the public function. You might consider sanitizing using something like this: [[ $1 =~ [_[:alpha:]][_[:alnum:]]* ]] then you won't have to worry that you're forgetting something. On Sat, May 1, 2010 at 5:19 PM, Freddy Vulto <fvu...@gmail.com> wrote: > Here's another revised version. > > It seems like a lot of bookkeeping (I wish we could transfer to bash?), > but I don't see another way if you want to pass "variables by reference" > in a bash library and prevent both yourself and public users from being > bitten by a conflict with a local variable - other than obfuscating your > library local variables. > > I hope we can get it right, so it can be used with a revised version of > the `_get_cword' function of the bash-completion package (= blackbox). > > There are still some questions: > > I have to give local variables a value (append an equal sign) in order > to get them listed with 'local' in "blackbox()". Is there a bash > builtin which lists all defined local variable names, even those not > having a value yet? > > I also want to let 'blackbox' return array variables, which doesn't seem > to be possible with the 'printf/read' workarounds, so I have to use > 'eval' and still need to sanitize the array variable names. What's > considered good sanitizing: I'm now checkin for $' \n\t;:$' in > "_blackbox_var_sane()"? > > Other changes are: > > Called private function _after_ collission test. > > Output error messages to stderr and return non-zero value. > > Added check to enforce private function "_blackbox()" is ALWAYS called > via public "blackbox()". > > I thought about whether private function "_blackbox()" should also have > a check for having local conflicts, but I figured this should be covered > by blackbox unit tests :-) > > Added an input parameter and return value for the sake of completeness. > > Here's the code: > > # Output error of variable by reference conflicting with local > # Params: $1 Variable name causing conflict > # $2 Function in which conflict occurs > _blackbox_var_conflict() { > echo "ERROR: variable name conflicts with local variable:"\ > "'$1', in function: $2()" 1>&2 > } > > # Check whether private function is being called by public interface > # Params: $1 Function name of public interface > # Return: False (1) if error > _blackbox_called_by() { > if [[ ${FUNCNAME[2]} != $1 ]]; then > echo "ERROR: ${FUNCNAME[1]}() MUST be called by $1()" 1>&2 > return 1 > fi > } > > # Check whether variable is sane to be used as eval assignment > # Param: $1 Variable name > # Return: False (1) if not sane > _blackbox_var_sane() { > if [[ ! $1 || ${1//[$' \n\t;:$']} != $1 ]]; then > echo "ERROR: invalid identifier: '$1'"\ > "passed to function: ${FUNCNAME[1]}()" 1>&2 > return 1 > fi > } > > # Private library function. Do not call directly. See blackbox() > _blackbox() { > _blackbox_called_by blackbox || return 1 > local a b c d e f g h i j arr=( foo "bar cee" ) > # ... > # Lots of complicated library code here > # ... > [[ $2 ]] && printf -v $2 %s b # Return value > _blackbox_var_sane "$3" && # Return array value > eval $3=\( \"\${a...@]}\" \) || return 1 > return 0 # Return exit status > } > > # Param: $1 input argument > # Param: $2 variable name to return value to > # Param: $3 variable name to return array value to > # Public library function > blackbox() { > # NOTE: Give all locals a value so they're listed with 'local' > local __2= __3= __x= __v= IFS=$'\n' > # Check arguments conflicting with locals > for __v in $(local); do > case ${__v%=*} in $2|$3) > _blackbox_var_conflict ${__v%=*} $FUNCNAME; return 1;; > esac > done > _blackbox "$1" __2 __3 # Call private function > __x=$? # Catch exit status > [[ $2 ]] && printf -v $2 %s "$__2" # Return value > _blackbox_var_sane "$3" && # Return array value > eval $3=\( \"\${_...@]}\" \) || return 1 > return $__x # Return exit status > } > > blackbox i a b; printf $'%s\n' $a "$...@]}" # Outputs vars all right > blackbox i __2 __3 # Outputs error > d='ls /;true'; blackbox i "$d" "$d" # No oops > _blackbox a # Force public access > > > Freddy Vulto > http://fvue.nl/wiki/Bash:_passing_variables_by_reference > > >