Sustitución doble y triple en bash y zsh

23

Seguimiento a la parte de fondo en esta pregunta .

En bashpuedo usar ${!FOO}para doble sustitución, en zsh ${(P)FOO}. En ambos, la vieja escuela (hack-y) eval \$$FOOfunciona.

Entonces, lo más inteligente y lógico para mí sería la ${${FOO}}, ${${${FOO}}}…sustitución doble / triple / n. ¿Por qué esto no funciona como se esperaba?

Segundo: ¿Qué hace el \en la evaldeclaración? Creo que es un escape, haciendo que algo sea eval \$$$FOOimposible. ¿Cómo hacer una sustitución triple / n con la que funciona en cada shell?

Profpatsch
fuente

Respuestas:

15

El \debe ser utilizado para prevenir la expansión de $$(proceso actual id). Para la sustitución triple, necesita doble evaluación, por lo que también hay más escapes para evitar las expansiones no deseadas en cada evaluación:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4
choroba
fuente
Sin embargo: l3=l2; eval eval eval echo \\\$\\$\$$l353294así que no es exactamente modular.
Profpatsch
2
¿Por qué no puedo hacer algo así eval $(eval echo \$$l2)? Odio bash, no tiene absolutamente ningún sentido.
Profpatsch
@Profpatsch: se agregaron más ejemplos.
choroba
Ah, es n². Ni siquiera quiero intentar entender por qué es así.
Profpatsch
2
@Profpatsch: Fácil. En cada paso, el número de mitades de barras invertidas (de hecho, es 2ⁿ).
choroba
10
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
Christopher Abad
fuente
6

Supongamos que el valor de FOOes un nombre de variable válido (digamos BAR), eval \$$FOOdivide el valor de BARen palabras separadas, trata cada palabra como un patrón comodín y ejecuta la primera palabra del resultado como un comando, pasando las otras palabras como argumentos. La barra invertida frente al dólar hace que sea tratado literalmente, por lo que el argumento pasó aeval builtin es la cadena de cuatro caracteres $BAR.

${${FOO}}es un error de sintaxis No hace una "doble sustitución" porque no existe tal característica en ninguno de los shells comunes (de todos modos no con esta sintaxis). En zsh, ${${FOO}} es válido y es una sustitución doble, pero se comporta de manera diferente a lo que desea: realiza dos transformaciones sucesivas en el valor deFOO , ambas de las cuales son la transformación de identidad, por lo que es solo una forma elegante de escribir ${FOO}.

Si desea tratar el valor de una variable como una variable, tenga cuidado de citar las cosas correctamente. Es mucho más fácil si establece el resultado en una variable:

eval "value=\${$FOO}"
Gilles 'SO- deja de ser malvado'
fuente
1
¿Establecerlo como variable para que la primera palabra no se use como comando? Hombre, este tipo de lógica es difícil de entender. Es el problema general que parece tener con bash.
Profpatsch
4

{ba,z}sh solución

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

Sin volverse loco con las citas, puede usarlo para muchos niveles de indirección de la siguiente manera:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

O si tiene más (!?!) Niveles de indirección, podría usar un bucle.

Advierte cuando se administra:

  • Entrada nula
  • 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 [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Tom Hale
fuente
Una solución cross-shell que funciona para mí. Esperemos que la "expansión de doble variable compatible con POSIX" redirija a esta respuesta en el futuro.
BlueDrink9
2

Al igual ${$foo}que no funciona en lugar de ${(P)foo}in zsh, tampoco lo hace ${${$foo}}. Solo necesita especificar cada nivel de indirección:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

Por supuesto, ${!${!foo}}no funciona bashporque bashno permite sustituciones anidadas.

chepner
fuente
Gracias, es bueno saberlo. Es una pena que esto sea solo zsh, por lo que realmente no puede incluirlo en un script (todos tienen bash, pero no es al revés).
Profpatsch
Sospecho que zshse instala con mucha más frecuencia de lo que podría suponer. Es posible que no esté vinculado a Me shgusta a bashmenudo (pero no siempre), pero aún puede estar disponible para los scripts de shell. Piensa en ello como lo haces con Perl o Python.
chepner
2

¿Por qué necesitarías hacer eso?

Siempre puedes hacerlo en varios pasos como:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

O use una función como:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

Luego:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW, en zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah
Stéphane Chazelas
fuente
Huh, usando varios pasos no se me ocurrió hasta ahora ... extraño.
Profpatsch
Es obvio, desde mi punto de vista, por qué @Profpatsch quiere hacer eso . Porque es la forma más intuitiva de hacerlo. Ser difícil implementar múltiples sustituciones en bash es otra cosa.
Nikos Alexandris
2

Solución POSIX

Sin volverse loco con las citas, puede usar esta función para muchos niveles de indirección de la siguiente manera:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

O si tiene más (!?!) Niveles de indirección, podría usar un bucle.

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
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}
Tom Hale
fuente
¿Hay alguna razón por la que no has combinado esta respuesta con la anterior? De un vistazo no veo la diferencia entre ellos.
BlueDrink9