$ {! FOO} et zsh

13

${!FOO}effectue une double substitution dans bash, ce qui signifie qu'il prend la valeur (chaîne) de FOO et l'utilise comme nom de variable.
zshne prend pas en charge cette fonctionnalité.

Existe-t-il un moyen de faire en sorte que cela fonctionne de la même manière dans bashet zsh?

Contexte:

J'ai une liste de variables d'environnement, comme

PATH MAIL EDITOR

et souhaitez d'abord imprimer les noms des variables et ensuite leurs valeurs.

Cela fonctionne bashmais pas zsh:

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

Cela devrait être en quelque sorte possible «à l'ancienne» avec eval, mais je ne peux pas le faire fonctionner:

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

Je ne comprendrai jamais pourquoi je ne peux pas simplement faire des substitutions profondes arbitraires comme ${${VAR}}ou même ${${${VAR}}}si besoin est, donc une explication serait bien aussi.

Profpatsch
la source
1
HAH! Je pensais que zsh était censé avoir plus de fonctionnalités.
Ярослав Рахматуллин
4
Pas besoin de se vanter bash, il a en effet la même fonction (et bien plus), n'utilise qu'un modèle différent, à savoir ${(p)FOO}.
Profpatsch
2
@ ЯрославРахматуллин, le (e)drapeau d'expansion des paramètres de zsh a été ajouté dans zsh-2.6-beta17 (mai 1996), le (P)drapeau dans 3.1.5-pws-7 (1999). bash's ${!var}en 2.0 (décembre 1996).
Stéphane Chazelas

Réponses:

18

Bash et zsh ont tous deux un moyen d'effectuer une expansion indirecte, mais ils utilisent une syntaxe différente.

Il est assez facile d'effectuer une expansion indirecte en utilisant eval; cela fonctionne dans tous les shells POSIX et la plupart des shells Bourne. Prenez soin de citer correctement au cas où la valeur contient des caractères qui ont une signification spéciale dans le shell.

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

${${VAR}}ne fonctionne pas car ce n'est pas une fonctionnalité implémentée par un shell. La chose à l'intérieur des accolades doit être conforme aux règles de syntaxe qui ne comprennent pas ${VAR}. (Dans zsh, cette syntaxe est prise en charge, mais fait quelque chose de différent: les substitutions imbriquées effectuent des transformations successives sur la même valeur; ${${VAR}}est équivalent à $VARcar cela effectue la transformation d'identité deux fois sur la valeur.)

Gilles 'SO- arrête d'être méchant'
la source
19

Le commentaire, sous la question d'origine, par Profpatsch donne l'exemple ${(p)FOO}de sortie du contenu d'une référence de variable indirecte. Le drapeau est incorrect ou une faute de frappe, il doit s'agir d'un P majuscule et non d'un p minuscule. Utilisation: ${(P)FOO}.

Les éléments suivants devraient produire la sortie souhaitée:

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

Depuis la page de manuel zshexpn ; section - Indicateurs d'extension des paramètres :

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'

À un moment donné, j'ai lu pourquoi ${${${VAR}}}ne produit pas la sortie que vous attendiez, mais à ce moment-là, je ne le trouve pas. Vous pouvez faire quelque chose comme ceci:

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

Vous n'utilisez pas eval correctement. Dans votre exemple, la valeur de $ VAR précédée d'un "$" (c'est-à-dire "$ VALUE") serait exécutée comme une commande. Ce n'est pas ce que tu veux. Vous souhaitez évaluer l'expansion d'une variable dont le nom est tiré d'une autre 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
Ярослав Рахматуллин
la source
0

{ba,z}sh Solution

Voici une fonction qui fonctionne à la fois {ba, z} sh. Je pense qu'il est également conforme à POSIX.

Il prévient lorsqu'il est donné:

  • Entrée nulle
  • Plus d'un argument
  • Un nom de variable non défini

# 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
la source
Il n'y a functionni ni [[...]]dans POSIX sh.
Stéphane Chazelas
La vérification de l'absence d'argument doit être effectuée avec [ "$#" -eq 0 ](ou probablement [ "$#" -ne 1 ]car vous n'attendez qu'un seul argument).
Stéphane Chazelas
@ StéphaneChazelas Merci, mis à jour. Existe-t-il un shell de référence pour la conformité POSIX? Je pense que ça shell-checkpourrait aider aussi.
Tom Hale
@ StéphaneChazelas Concernant le test, je veux aussi vouloir échouer quand on me donne un seul argument nul. J'ai ajouté votre 2e suggestion. À votre santé!
Tom Hale
Je suis d'accord avec la citation ${1-}donnée a='"" -o -n 1' [ -z $a ]== 0. Mais ne sera-t-il pas $#toujours un seul jeton?
Tom Hale