Skip to content

Instantly share code, notes, and snippets.

@ormaaj
Last active March 30, 2021 13:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ormaaj/5682807 to your computer and use it in GitHub Desktop.
Save ormaaj/5682807 to your computer and use it in GitHub Desktop.
Broken namerefs.
#!/usr/bin/env bash
# Only ksh93 has "real" C++-like references (called namerefs). They can be used
# as reference parameters for passing data structures like arrays in and out of
# functions. Bash 4.3+ and mksh also have a nameref feature, but unlike ksh93,
# which has three different kinds of nameref, bash supports just two of these
# and mksh only one (the for-loop type won't be illustrated here). It is the
# "dynamic" kind of nameref that is supported by all three, which is mostly
# just sugar for Bash's old "${!var}" behavior (ksh93 also uses this kind when
# a nameref doesn't refer to a positional parameter).
# When attempting to pass data between functions, dynamic namerefs are
# technically not much better than eval-based solutions because they don't
# guarantee that a reference parameter actually points to the same object
# instance when dereferenced as it did when defined. Reference parameters in
# Bash and mksh dynamically point to whatever variable of a particular name is
# visible from the current context at the time of dereference.
# Keep this in mind when using shell libraries. This type of nameref is not
# robust. It's up to the caller of a function that uses indirection to ensure
# there are no local name collisions all the way up the call chain. There is no
# proper "information hiding" or "encapsulation" in these langauges.
# This testcase demonstrates the problem by attempting to pass two different
# instances of array variables named `x' to a function at the same time, while
# also recycling the names `x' and `y' as the names of local reference
# variables.
${BASH_VERSION+shopt -s extglob lastpipe}
function main {
typeset sh
for sh; do
printf '%s: %s\n' "$sh" "$("$sh" -s </dev/fd/3 2>&1)"
done
} 4<&0 <<\EOF 3<&0 <&4-
function f {
case $1 in
0)
typeset -a x
x=(I\'m an x)
f 1 x
;;
1)
typeset -n y=$2
typeset -a x
x=(I\'m a different x)
f 2 x y # The big problem occurs here!
;;
2)
typeset -n x=$2 y=$3
printf '%s=(%s) ' "${!x}" "${x[*]}" "${!y}" "${y[*]}"
echo
esac
}
f 0
EOF
main {{,m}k,ba}sh
# main {,m}ksh /home/ormaaj/doc/programs/bash+
# Output:
# ksh: x=(I'm a different x) x=(I'm an x)
# mksh: x=(I'm a different x) x=(I'm a different x)
# bash: line 15: typeset: x: nameref variable self references not allowed
# bash: line 15: typeset: y: nameref variable self references not allowed
# =(I'm a different x) x=(I'm a different x)
# Ksh93 handles all these collisions gracefully. Bash and mksh do not. Note you
# should never have this problem within a single function like above. The
# problem primarily applies to consumers of library functions and to large
# programs with many functions.
# vim: set fenc=utf-8 ff=unix ft=sh :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment