Il semble que la méthode recommandée pour définir une variable indirecte dans bash consiste à utiliser eval
:
var=x; val=foo
eval $var=$val
echo $x # --> foo
Le problème est celui habituel avec eval
:
var=x; val=1$'\n'pwd
eval $var=$val # bad output here
(et comme il est recommandé dans de nombreux endroits, je me demande combien de scripts sont vulnérables à cause de cela ...)
Dans tous les cas, la solution évidente d'utiliser des guillemets (échappés) ne fonctionne pas vraiment:
var=x; val=1\"$'\n'pwd\"
eval $var=\"$val\" # fail with the above
Le fait est que bash a une référence de variable indirecte (avec ${!foo}
), mais je ne vois pas de moyen de faire une affectation indirecte - existe-t-il un moyen sensé de le faire?
Pour mémoire, j'ai trouvé une solution, mais ce n'est pas quelque chose que je considérerais "sain d'esprit" ...:
eval "$var='"${val//\'/\'\"\'\"\'}"'"
Le point essentiel est que la manière recommandée de procéder est la suivante:
eval "$var=\$val"
avec le RHS fait indirectement aussi. Étant donné que eval
est utilisé dans le même environnement , Il sera lié à $val
. Le report est donc efficace et, depuisnow, il ne s'agit que d'une variable. Étant donné que la variable $val
a un nom connu, La citation ne pose aucun problème et elle aurait même pu être écrite ainsi:
eval $var=\$val
Mais comme il est préférable de toujours ajouter des guillemets, le premier est meilleur, ou Même ceci:
eval "$var=\"\$val\""
Une meilleure alternative dans bash qui a été mentionnée pour tout ce que Évite eval
complètement (et n’est pas aussi subtile que declare
etc.):
printf -v "$var" "%s" "$val"
Bien que ce ne soit pas une réponse directe à ce que j’avais demandé à l’origine ...
Une solution légèrement meilleure, en évitant les conséquences possibles de l’utilisation de eval
sur la sécurité, est:
declare "$var=$val"
Notez que declare
est un synonyme de typeset
dans bash
. La commande typeset
est plus largement prise en charge (ksh
et zsh
l'utilisent également):
typeset "$var=$val"
Dans les versions modernes de bash
, on devrait utiliser un nameref.
declare -n var=x
x=$val
C'est plus sûr que eval
, mais pas parfait.
Bash a une extension à printf
qui enregistre son résultat dans une variable:
printf -v "${VARNAME}" '%s' "${VALUE}"
Cela empêche tous les problèmes d'échappement possibles.
Si vous utilisez un identificateur non valide pour $VARNAME
, la commande échouera et renverra le code d'état 2:
$ printf -v ';;;' foobar; echo $?
bash: printf: `;;;': not a valid identifier
2
eval "$var=\$val"
L'argument de eval
doit toujours être une chaîne unique entourée de guillemets simples ou doubles. Tout code qui diffère de ce modèle présente un comportement inattendu dans les cas Edge, tels que les noms de fichiers avec des caractères spéciaux.
Lorsque le shell développe l’argument de eval
, le $var
est remplacé par le nom de la variable et le \$
par un dollar simple. La chaîne qui est évaluée devient donc:
varname=$value
C'est exactement ce que vous voulez.
En règle générale, toutes les expressions de la forme $varname
doivent être placées entre guillemets. Les guillemets peuvent être omis à deux endroits seulement: affectations de variables et case
. Puisqu'il s'agit d'une affectation de variable, les guillemets ne sont pas nécessaires ici. Cependant, ils ne font pas mal, vous pouvez donc écrire le code original ainsi:
eval "$var=\"the value is $val\""
Les versions les plus récentes de bash supportent quelque chose appelé "transformation de paramètre", documenté dans une section du même nom dans bash (1).
"${value@Q}"
se développe en une version citée par Shell de "${value}"
que vous pouvez réutiliser en tant qu'entrée.
Ce qui signifie que ce qui suit est une solution sûre:
eval="${varname}=${value@Q}"
Juste pour être complet, je veux aussi suggérer l’utilisation possible du bash intégré en lecture. J'ai également apporté des corrections concernant -d '' sur la base des commentaires de socowi.
Il faut cependant faire très attention lorsque vous utilisez read pour vous assurer que l’entrée est désinfectée (-d '' lit jusqu’à la fin de null et printf "...\0" termine la valeur avec un null), et que la lecture elle-même est exécutée Shell principal où la variable est nécessaire et non un sous-shell (d'où la syntaxe <<(...)).
var=x; val=foo0shouldnotterminateearly
read -d'' -r "$var" < <(printf "$val\0")
echo $x # --> foo0shouldnotterminateearly
echo ${!var} # --> foo0shouldnotterminateearly
J'ai testé cela avec\n\t\r espaces et 0, etc. cela a fonctionné comme prévu sur ma version de bash.
Le -r évitera d'échapper à \, donc si vous avez les caractères "\" et "n" dans votre valeur et pas une nouvelle ligne, x contiendra également les deux caractères "\" et "n".
Cette méthode n’est peut-être pas aussi esthétique que la solution eval ou printf et serait plus utile si la valeur provient d’un fichier ou d’un autre descripteur de fichier d’entrée.
read -d'' -r "$var" < <( cat $file )
Et voici quelques suggestions alternatives pour la syntaxe <<()
read -d'' -r "$var" <<< "$val"$'\0'
read -d'' -r "$var" < <(printf "$val") #Apparently I didn't even need the \0, the printf process ending was enough to trigger the read to finish.
read -d'' -r "$var" <<< $(printf "$val")
read -d'' -r "$var" <<< "$val"
read -d'' -r "$var" < <(printf "$val")