$ {! FOO} e zsh

13

${!FOO}executa uma substituição dupla em bash, o que significa que pega o valor (string) de FOO e o usa como um nome de variável.
zshnão suporta esse recurso.

Existe uma maneira de fazer isso funcionar da mesma maneira bashe zsh?

Fundo:

Eu tenho uma lista de variáveis ​​de ambiente, como

PATH MAIL EDITOR

e deseja primeiro imprimir os nomes das variáveis ​​e depois seus valores.

Isso funciona, bashmas não zsh:

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

Deveria ser de alguma forma possível "do jeito antigo" eval, mas não consigo fazer isso funcionar:

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

Eu nunca vou entender por que não posso simplesmente fazer substituições profundas arbitrárias como ${${VAR}}ou mesmo ${${${VAR}}}se necessário, então uma explicação para isso também seria legal.

Profpatsch
fonte
1
HAH! Eu pensei que o zsh deveria ter mais recursos.
Ярослав Рахматуллин
4
Não há necessidade de se gabar bash, ele realmente tem a mesma função (e muito mais), apenas usa um padrão diferente, a saber ${(p)FOO}.
Profpatsch
2
@ ЯрославРахматуллин, o (e)sinalizador de expansão de parâmetro do zsh foi adicionado no zsh-2.6-beta17 (maio de 1996), o (P)sinalizador em 3.1.5-pws-7 (1999). bash's ${!var}em 2.0 (dezembro de 1996).
Stéphane Chazelas

Respostas:

18

Tanto o bash quanto o zsh têm uma maneira de executar expansão indireta, mas usam sintaxe diferente.

É fácil o suficiente para executar expansão indireta usando eval; isso funciona em todos os shell POSIX e na maioria dos Bourne. Lembre-se de citar corretamente, caso o valor contenha caracteres que tenham um significado especial no shell.

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

${${VAR}}não funciona porque não é um recurso que qualquer shell implemente. A coisa dentro dos chavetas deve estar em conformidade com as regras de sintaxe que não incluem ${VAR}. (No zsh, isso é suportado pela sintaxe, mas faz algo diferente: substituições aninhadas executam transformações sucessivas no mesmo valor; ${${VAR}}é equivalente a $VARuma vez que isso executa a transformação de identidade duas vezes no valor.)

Gilles 'SO- parar de ser mau'
fonte
19

O comentário, sob a pergunta original, de Profpatsch, fornece o exemplo ${(p)FOO}para gerar o conteúdo de uma referência variável indireta. O sinalizador está incorreto ou é um erro de digitação, deve ser um P maiúsculo e não um p minúsculo. Use: ${(P)FOO}.

O seguinte deve produzir a saída desejada:

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

Na página do manual zshexpn ; seção - Sinalizadores de expansão de parâmetro :

P

  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'

Ao mesmo tempo, li por ${${${VAR}}}que não produz a saída que você esperava, mas neste momento não consigo encontrá-la. Você pode fazer algo como o seguinte:

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

Você não está usando o eval corretamente. No seu exemplo, o valor de $ VAR precedido por um "$" (ou seja, `$ VALUE ') seria executado como um comando. Não é isso que você quer. Você deseja avaliar a expansão de uma variável cujo nome é obtido de outra variável.

$ 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
Ярослав Рахматуллин
fonte
0

{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.

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
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
fonte
Não há functionnem [[...]]no POSIX sh.
Stéphane Chazelas
A verificação de nenhum argumento deve estar com [ "$#" -eq 0 ](ou provavelmente [ "$#" -ne 1 ]como você espera apenas um argumento).
Stéphane Chazelas
@ StéphaneChazelas Obrigado, atualizado. Existe um shell de referência para conformidade com POSIX? Estou pensando que shell-checkpode ajudar também.
Tom Hale
@ StéphaneChazelas No teste, também quero querer falhar quando receber um único argumento nulo. Adicionei sua segunda sugestão. Felicidades!
Tom Hale
Eu concordo com a citação ${1-}dada a='"" -o -n 1' [ -z $a ]== 0. Mas nem $#sempre será um único token?
Tom Hale