Skip to content

Instantly share code, notes, and snippets.

@ormaaj
Created June 4, 2015 00:53
Show Gist options
  • Select an option

  • Save ormaaj/1adca7349901295dfa49 to your computer and use it in GitHub Desktop.

Select an option

Save ormaaj/1adca7349901295dfa49 to your computer and use it in GitHub Desktop.
To hopefully add some insight, here's briefly my understanding of how
namerefs are supposed to work in these cases given the 3 implementations
I'm aware of.
Between ksh93, bash, and mksh, there are 3 different types of namerefs.
All three support "dynamic" namerefs, where each time a variable is
referenced, the shell looks at the currently visible variable whose name
is pointed to by the reference. All namerefs in bash and mksh are of
this type.
Ksh93 adds "reference parameters". These resemble C++ references in many
ways (at least, I strongly suspect they're modeled after C++). The way
ksh decides this is undocumented AFAICT. It occurs whenever a nameref
points to a positional parameter as determined prior to expansion when
the `typeset' command is parsed. This is analagous to the way bash
determines whether a declaration command's argument is an array
assignment at the time it parses words.
So ksh considers this `ref' a reference parameter:
# ksh -xc 'function g { typeset x="in g"; typeset -n ref=$1; echo "$ref"; }; function f { typeset x="in f"; g x; }; typeset -ft f g; f'
+ typeset -ft f g
+ f
+ x='in f'
+ typeset x
+ g x
+ x='in g'
+ typeset x
+ ref=x
+ typeset -n ref
+ echo 'in f'
in f
Adding an eval forces `g' to consider `ref' a dynamic reference, resulting in
bash-like behavior, except with static instead of dynamic scope.
# ksh -xc 'function g { typeset x="in g"; eval "typeset -n ref=$1"; echo "$ref"; }; function f { typeset x="in f"; g x; }; typeset -ft f g; f'
+ typeset -ft f g
+ f
+ x='in f'
+ typeset x
+ g x
+ x='in g'
+ typeset x
+ eval typeset -n ref=x
+ ref=x
+ typeset -n ref
+ echo 'in g'
in g
Third, bash and ksh93 support a type of nameref used with for-loops when
a variable is declared with the -n attribute and no value. Bash treats
the value of each parameter as a dynamic ref (because that's all it
supports) and relies on dynamic scope as usual, while ksh93 treats each
name as a reference parameter.
Essentially the major differences between the shells are ksh93
supporting an additional type of nameref and having static scope, and
mksh not supporting the for-loop feature. Most of the testcases in this
thread only look at dynamic references to global variables declared in
the same scope. That's probably fine for Bash. I'd just note that there
are probably quite a few differences when looking at how ksh reference
args behave - even between reference args that point to globals versus
locals. I've noticed a lot of these in the past but it's difficult to
remember everything and come up with a comprehensive set of testcases.
As discussed in previous threads, one of the problems with ksh93 is its
failure to perform expansions on array indices, which makes certain keys
of associative arrays either completely unusable or not usable in a
portable way. It has a "magical" way of inferring the value of the key
without expanding the index like bash does. That's why `test3' in the
following testcase fails in bash, but is the only one of these that
works in ksh93 even with `]' as a key. That's relevant to one of Greg's
concerns. Again IMO the bash way is more consistent with the rest of the
language and not dangerous if used properly.
#!/usr/bin/env bash
# Pass 'array[index]'
function test1 {
typeset -n pArr=$1
printf -- '%s: %s = %s, $((%s)) = %s\n' "${FUNCNAME:-${.sh.fun}}" "${!pArr}" "${pArr-fail}" 'pArr' "$((pArr))"
}
# Pass 'array[index]' with a local name conflict (x)
function test2 {
typeset -n pArr=$1
typeset x=foo
printf -- '%s: %s = %s, $((%s)) = %s\n' "${FUNCNAME:-${.sh.fun}}" "${!pArr}" "${pArr-fail}" 'pArr' "$((pArr))"
}
# Pass the array and key as separate arguments and reference the subscript by value.
function test3 {
typeset -n pArr=$1
typeset x=$2
printf -- '%s: %s = %s, $((%s)) = %s\n' "${FUNCNAME:-${.sh.fun}}" "${!pArr}" "${pArr[$x]}" "pArr[$x]" "$((pArr[$x]))"
}
# Pass both array and key separately by reference.
function test4 {
typeset -n pArr=$1 pKey=$2
printf -- '%s: %s = %s, $((%s)) = %s\n' "${FUNCNAME:-${.sh.fun}}" "${!pArr}" "${pArr[$pKey]-fail}" 'pArr[$pKey]' "$((pArr[\$pKey]))"
}
function main {
typeset -A a=(
[foo]=1
[bar]=2
[\]]=3
)
# x is the key for each test. eval forces continuing on syntax errors.
typeset testCode x
for x in bar \]; do
while IFS= read -r testCode; do
command eval "$testCode"
done </dev/fd/3
done 3<<-'CODE'
test1 'a[$x]'
test2 'a[$x]'
test1 'a[$2]' "$x"
test2 'a[$2]' "$x"
test3 a "$x" # expected syntax error in bash
test4 a x # expected fail in ksh for arithmetic expansion.
CODE
}
if [[ ${!KSH_VERSION} == .sh.version && -o xtrace ]]; then
typeset -ft main test{1..4}
fi
main "$@"
# output:
# $ bash ./arrkeys.ksh
# test1: a[$x] = 2, $((pArr)) = 0
# test2: a[$x] = 1, $((pArr)) = 0
# test1: a[$2] = 2, $((pArr)) = 0
# test2: a[$2] = 2, $((pArr)) = 0
# test3: a = 2, $((pArr[bar])) = 2
# test4: a = 2, $((pArr[$pKey])) = 2
# test1: a[$x] = 3, $((pArr)) = 0
# test2: a[$x] = 1, $((pArr)) = 0
# test1: a[$2] = 3, $((pArr)) = 0
# test2: a[$2] = 3, $((pArr)) = 0
# ./arrkeys.ksh: line 21: pArr[]]: syntax error: invalid arithmetic operator (error token is "]")
# test4: a = 3, $((pArr[$pKey])) = 3
# $ ksh ./arrkeys.ksh
# test1: a[$x] = fail, $((pArr)) = 0
# test2: a[$x] = fail, $((pArr)) = 0
# test1: a[$2] = fail, $((pArr)) = 0
# test2: a[$2] = fail, $((pArr)) = 0
# test3: a = 2, $((pArr[bar])) = 2
# test4: a = 2, $((pArr[$pKey])) = 2
# test1: a[$x] = fail, $((pArr)) = 0
# test2: a[$x] = fail, $((pArr)) = 0
# test1: a[$2] = fail, $((pArr)) = 0
# test2: a[$2] = fail, $((pArr)) = 0
# test3: a = 3, $((pArr[]])) = 3
# test4: a = 3, $((pArr[$pKey])) = 0
I'm a bit surprised that so many of these arithmetic expansion cases
give a `0' in bash. I'm not certain that's correct.
I hope that's useful to someone. It's probably nothing new to others.
--
Dan Douglas
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment