$ {! FOO} y zsh

13

${!FOO}realiza una doble sustitución en bash, lo que significa que toma el valor (cadena) de FOO y lo usa como un nombre de variable.
zshno es compatible con esta función.

¿Hay alguna manera de hacer que esto funcione igual en bashy zsh?

Antecedentes:

Tengo una lista de variables de entorno, como

PATH MAIL EDITOR

y desea imprimir primero los nombres de las variables y luego sus valores.

Esto funciona bashpero no en zsh:

for VAR in LIST
do
        echo $VAR
        echo ${!VAR}
done

De alguna manera debería ser posible "a la antigua usanza" eval, pero no puedo hacer que funcione:

for VAR in LIST
do
        echo $VAR
        echo `eval \$$VAR`
done

Nunca voy a entender por qué no puedo simplemente hacer sustituciones profundas arbitrarias como ${${VAR}}o incluso ${${${VAR}}}si es necesario, por lo que una explicación para eso también sería agradable.

Profpatsch
fuente
1
HAH! Pensé que se suponía que zsh tenía más funciones.
Ярослав Рахматуллин
44
No es necesario presumir bash, de hecho tiene la misma función (y mucho más), solo usa un patrón diferente, a saber ${(p)FOO}.
Profpatsch
2
@ ЯрославРахматуллин, el (e)indicador de expansión de parámetros de zsh se agregó en zsh-2.6-beta17 (mayo de 1996), el (P)indicador en 3.1.5-pws-7 (1999). bash ${!var}en 2.0 (diciembre de 1996).
Stéphane Chazelas

Respuestas:

18

Tanto bash como zsh tienen una forma de realizar una expansión indirecta, pero usan una sintaxis diferente.

Es bastante fácil realizar una expansión indirecta usando eval; esto funciona en todos los POSIX y la mayoría de los shells Bourne. Tenga cuidado de citar correctamente en caso de que el valor contenga caracteres que tengan un significado especial en el shell.

eval "value=\"\${$VAR}\""
echo "$VAR"
echo "$value"

${${VAR}}no funciona porque no es una característica que implementa ningún shell. La cosa dentro de las llaves debe ajustarse a las reglas de sintaxis que no incluyen ${VAR}. (En zsh, esta es una sintaxis compatible, pero hace algo diferente: las sustituciones anidadas realizan transformaciones sucesivas en el mismo valor; ${${VAR}}es equivalente a, $VARya que esto realiza la transformación de identidad dos veces en el valor).

Gilles 'SO- deja de ser malvado'
fuente
19

El comentario, bajo la pregunta original, de Profpatsch da el ejemplo ${(p)FOO}para generar el contenido de una referencia de variable indirecta. La bandera es incorrecta o un error tipográfico, debe ser una P mayúscula y no una minúscula p. Uso: ${(P)FOO}.

Lo siguiente debería producir el resultado deseado:

#! /bin/zsh
LIST=(PATH MAIL EDITOR)
for VAR in ${LIST}
do
    print "${VAR}:  ${(P)VAR}"
done

Desde la página de manual de zshexpn ; sección - Indicadores de expansión de parámetros :

PAG

  This forces the value of the parameter name to be interpreted as a further
  parameter name,  whose  value  will  be used where appropriate.  Note that
  flags set with one of the typeset family of commands (in particular case
  transformations) are not applied to the value of name used in this fashion.

  If used with a nested parameter or command substitution, the result of that
  will be taken as a parameter  name  in the  same  way.   For  example,  if
  you  have  `foo=bar'  and `bar=baz', the strings ${(P)foo}, ${(P)${foo}}, and
  ${(P)$(echo bar)} will be expanded to `baz'

En un momento leí por qué ${${${VAR}}}no produce el resultado que esperaba, pero en este momento no puedo encontrarlo. Puedes hacer algo como lo siguiente:

first="second" ; second="third" ; third="fourth" ; fourth="fifth"
print ${(P)${(P)${(P)first}}}
fifth
Friartek
fuente
3

No estás utilizando eval correctamente. En su ejemplo, el valor de $ VAR precedido por un "$" (es decir, `$ VALOR ') se ejecutará como un comando. Eso no es lo que quieres. Desea evaluar la expansión de una variable cuyo nombre se toma de otra variable.

$ for i in `echo PATH MAIL EDITOR`; 
    do eval moo="\${$i}" 
    echo $moo 
done
/usr/local/sbin:/usr/local/bin:/usr/sbin:/u (...)
/var/mail/root
nano
Ярослав Рахматуллин
fuente
0

{ba,z}sh solución

Aquí hay una función que funciona tanto en {ba, z} sh. Creo que también es compatible con POSIX.

Advierte cuando se administra:

  • Entrada nula
  • Más de un argumento
  • Un nombre de variable que no está configurado

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one non-empty argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Tom Hale
fuente
No hay functionni [[...]]en POSIX sh.
Stéphane Chazelas
La comprobación de ningún argumento debe estar con [ "$#" -eq 0 ](o probablemente [ "$#" -ne 1 ]ya que solo espera un argumento).
Stéphane Chazelas
@ StéphaneChazelas Gracias, actualizado. ¿Existe un shell de referencia para el cumplimiento POSIX? Estoy pensando que shell-checkpodría ayudar también.
Tom Hale
@ StéphaneChazelas Re la prueba, también quiero fallar cuando se me da un solo argumento nulo. He añadido tu segunda sugerencia. ¡Salud!
Tom Hale
Estoy de acuerdo con la cita ${1-}dada a='"" -o -n 1' [ -z $a ]== 0. ¿Pero no $#siempre será una sola ficha?
Tom Hale