script - Asignación indirecta de variables en bash
string shell script (5)
Bash tiene una extensión para printf
que guarda su resultado en una variable:
printf -v "${VARNAME}" ''%s'' "${VALUE}"
Esto evita todos los posibles problemas de escape.
Si usa un identificador inválido para $VARNAME
, el comando fallará y devolverá el código de estado 2:
$ printf -v '';;;'' foobar; echo $?
bash: printf: `;;;'': not a valid identifier
2
Parece que la forma recomendada de hacer ajuste de variable indirecta en bash es usar eval
:
var=x; val=foo
eval $var=$val
echo $x # --> foo
El problema es el habitual con eval
:
var=x; val=1$''/n''pwd
eval $var=$val # bad output here
(y como se recomienda en muchos lugares, me pregunto cuántos guiones son vulnerables debido a esto ...)
En cualquier caso, la solución obvia de usar citas (escapadas) realmente no funciona:
var=x; val=1/"$''/n''pwd/"
eval $var=/"$val/" # fail with the above
La cuestión es que bash tiene referencia de variable indirecta horneada (con ${!foo}
), pero no veo ninguna manera de hacer una asignación indirecta. ¿Hay alguna forma sensata de hacerlo?
Para el registro, encontré una solución, pero esto no es algo que consideraría "sensato" ...
eval "$var=''"${val///'//'/"/'/"/'}"''"
El punto principal es que la forma recomendada de hacerlo es:
eval "$var=/$val"
con el RHS hecho indirectamente también. Como eval
se utiliza en el mismo entorno, tendrá $val
bound, por lo que diferir funciona, y desde ahora es solo una variable. Dado que la variable $val
tiene un nombre conocido, no hay problemas con las cotizaciones, y podría incluso haber sido escrito como:
eval $var=/$val
Pero como es mejor agregar siempre comillas, la primera es mejor, o incluso esto:
eval "$var=/"/$val/""
Una mejor alternativa en bash que se mencionó para todo lo que evita la eval
completo (y no es tan sutil como declare
etc.):
printf -v "$var" "%s" "$val"
Aunque esta no es una respuesta directa, lo que originalmente pedí ...
Las versiones más nuevas de bash admiten algo llamado "transformación de parámetros", documentado en una sección del mismo nombre en bash (1).
"${value@Q}"
expande a una versión citada por el shell de "${value}"
que puede volver a utilizar como entrada.
Lo que significa que la siguiente es una solución segura:
eval="${varname}=${value@Q}"
Una forma un poco mejor, evitando las posibles implicaciones de seguridad de usar eval
, es
declare $var="$val"
Tenga en cuenta que declare
es un sinónimo de typeset
en bash
. El comando typeset
es más ampliamente compatible ( ksh
y zsh
también lo usan):
typeset $var="$val"
eval "$var=/$val"
El argumento para eval
siempre debe ser una sola cadena encerrada entre comillas simples o dobles. Todo el código que se desvía de este patrón tiene un comportamiento involuntario en casos extremos, como nombres de archivos con caracteres especiales.
Cuando el argumento para eval
se expande por el shell, el $var
se reemplaza con el nombre de la variable, y el /$
se reemplaza por un simple dólar. La cadena que se evalúa se convierte en:
varname=$value
Esto es exactamente lo que quieres.
Por lo general, todas las expresiones de la forma $varname
deben estar entre comillas dobles. Solo hay dos lugares donde se pueden omitir las citas: asignaciones de variables y case
y case
. Como esta es una asignación variable, las comillas no son necesarias aquí. Sin embargo, no duelen, por lo que también puedes escribir el código original como:
eval "$var=/"the value is $val/""