Created
June 4, 2015 00:53
-
-
Save ormaaj/1adca7349901295dfa49 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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