zsh: How to get value of variable based on a another variable?

With zsh:

  • ${(P)varname} expands to the value of the variable whose name is stored in $varname. So if $varname contains var, ${(P)varname} expands to the same thing as $var.
  • ${(e)var} expands to the content of $var but also performs parameter expansions, command substitution and arithmetic expansion within. So if $var contains $othervar, ${(e)var} expands to the same thing as $othervar.
  • You can nest variable expansion operators, so things like ${(P)${var:-something}} work
  • ${:-content} is one way to have a parameter expansion expand to arbitrary text (here content)

(see the manual for details)

So you could do:

_v1=windows
_v2_windows=/mnt/d
printf '%s\n' ${(P)${:-_v2_$_v1}}

Or:

printf '%s\n' ${(e)${:-\$_v2_$_v1}}

Or do it in two steps:

varname=_v2_$_v1
printf '%s\n' ${(P)varname}

Or:

expansions_to_evaluate=\$_v2_$_v1

printf '%s\n' ${(e)expansions_to_evaluate}

Or you could use the standard POSIX syntax:

eval 'printf "%s\n" "${_v2_'"$_v1"'}"'

Beware that if the value of $_v1 is not under your control, all those amount to an arbitrary command injection vulnerability, you'd need to sanitize the value first.

Also note that zsh supports associative arrays (and has long before bash), so you can do:

typeset -A mnt
mnt=(
  windows /mnt/d
  osx     /Volumes/d
)
os=windows
printf '%s\n' $mnt[$os]

Which would be a lot more legible, and wouldn't have any of those security implications.


printf '%s\n' "${(P)$(echo "_v2_$_v1")}"

alternatively

var=_v2_$_v1
printf '%s\n' "${(P)var}"

In both cases, the parameter expansion flag (P) is used to expand the name inside ${...} into the name of the actual variable that we'd like to get the value of.

This is similar to variable indirection with ${!...} in the bash shell.

Tags:

Zsh

Variable