Substituição dupla e tripla em bash e zsh

22

Acompanhamento da parte de fundo desta pergunta .

Em basheu posso usar ${!FOO}para substituição dupla, em zsh ${(P)FOO}. Em ambos, a velha escola (hack-y) eval \$$FOOfunciona.

Então, a coisa mais inteligente e lógica para mim seria a ${${FOO}}, ${${${FOO}}}…substituição dupla / tripla / n. Por que isso não funciona como o esperado?

Segundo: O que \faz na evaldeclaração? Eu acho que é uma fuga, tornando algo como eval \$$$FOOimpossível. Como fazer uma substituição tripla / n pela que funciona em todos os shell?

Profpatsch
fonte

Respostas:

15

A \deve ser utilizado para evitar a expansão da $$(corrente ID de processo). Para a substituição tripla, você precisa de avaliação dupla, assim também mais escapes para evitar as expansões indesejadas em cada avaliação:

#! /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
fonte
No entanto: l3=l2; eval eval eval echo \\\$\\$\$$l353294portanto, não exatamente modular.
Profpatsch 15/03
2
Por que não posso fazer algo assim eval $(eval echo \$$l2)? Eu odeio bash, não faz absolutamente nenhum sentido.
Profpatsch 15/03
@ Profofatsch: Adicionado mais exemplos.
choroba
Ah, é n². Eu nem quero tentar entender por que é assim.
Profpatsch
2
@ Profofatsch: Fácil. Em cada etapa, o número de barras invertidas é reduzido pela metade (portanto, é 2ⁿ, de fato).
choroba
10
#!/bin/bash

hello=world
echo=hello

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

Supondo que o valor de FOOseja um nome de variável válido (digamos BAR), eval \$$FOOdivida o valor de BARem palavras separadas, trate cada palavra como um padrão curinga e execute a primeira palavra do resultado como um comando, passando as outras palavras como argumentos. A barra invertida na frente do dólar faz com que seja tratado literalmente, então o argumento passado para oeval interno é a cadeia de quatro caracteres $BAR.

${${FOO}}é um erro de sintaxe. Ele não faz uma "substituição dupla" porque não existe esse recurso em nenhum dos shells comuns (de qualquer maneira, não com esta sintaxe). No zsh, ${${FOO}} é válido e é uma substituição dupla, mas se comporta de maneira diferente do que você gostaria: executa duas transformações sucessivas no valor deFOO , sendo ambas a transformação de identidade, portanto, é apenas uma maneira elegante de escrever ${FOO}.

Se você deseja tratar o valor de uma variável como uma variável, tenha cuidado ao citar as coisas corretamente. É muito mais fácil se você definir o resultado para uma variável:

eval "value=\${$FOO}"
Gilles 'SO- parar de ser mau'
fonte
1
Definindo-o como variável, para que a primeira palavra não seja usada como comando? Cara, esse tipo de lógica é difícil de entender. É o problema geral que pareço ter com o bash.
Profpatsch
4

{ba,z}sh solução

Aqui está uma função que funciona em ambos {ba,z}sh. Acredito que também seja compatível com POSIX.

Sem enlouquecer com a citação, você pode usá-lo para muitos níveis de indireção, como:

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

Ou, se você tiver mais (!?!) Níveis de indireção, poderá usar um loop.

Ele avisa quando dado:

  • Entrada nula
  • Um nome de variável que não está definido

# 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
fonte
Uma solução cross-shell de trabalho para mim. Esperamos que a "expansão de variável dupla compatível com POSIX" redirecione para esta resposta no futuro.
BlueDrink9
2

Assim como ${$foo}não funciona no lugar de ${(P)foo}in zsh, nem funciona ${${$foo}}. Você só precisa especificar cada nível de indireção:

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

Obviamente, ${!${!foo}}não funciona bash, porque bashnão permite substituições aninhadas.

chepner
fonte
Obrigado, é bom saber. Uma pena que isso seja apenas para o zsh, então você realmente não pode colocá-lo em um script (todo mundo tem o bash, mas não é o contrário).
Profpatsch 24/03
Eu suspeito que zshesteja instalado com muito mais frequência do que você imagina. Pode não estar vinculado shcomo bashcostuma ser (mas nem sempre), mas ainda pode estar disponível para scripts de shell. Pense nisso como você faz Perl ou Python.
chepner
2

Por que você precisaria fazer isso?

Você sempre pode fazer isso em várias etapas, como:

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

Ou use uma função como:

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

Então:

$ 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, em zsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah
Stéphane Chazelas
fonte
Huh, usar várias etapas não me ocorreu até agora ... estranho.
Profpatsch 24/03
É óbvio, do meu ponto de vista, por que @Profpatsch quer fazer isso . Porque é a maneira mais intuitiva de fazer isso. Ser difícil implementar várias substituições no bash é outra coisa.
Nikos Alexandris
2

Solução POSIX

Sem enlouquecer com a citação, você pode usar esta função para muitos níveis de indireção, como:

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

Ou, se você tiver mais (!?!) Níveis de indireção, poderá usar um loop.

Ele avisa quando dado:

  • Entrada nula
  • Mais de um argumento
  • Um nome de variável que não está definido

# 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
fonte
Existe uma razão para você não combinar esta resposta com a anterior? De relance, não vejo a diferença entre eles.
BlueDrink9