Asignación de variables indirectas en bash
Parece que la forma recomendada de hacer el ajuste de variables indirectas 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 ya que se recomienda en muchos lugares, me pregunto cuántos scripts son vulnerables debido a esto...)
En cualquier caso, la solución obvia de usar comillas (escapadas) realmente no funciona:
var=x; val=1\"$'\n'pwd\"
eval $var=\"$val\" # fail with the above
La cosa es que bash tiene referencia de variable indirecta horneada (con ${!foo}
), pero no veo ninguna tal manera de hacer la asignación indirecta is ¿hay alguna manera sana de hacer esto?
Para que conste, encontré una solución, pero esto no es algo que consideraría "cuerdo"...:
eval "$var='"${val//\'/\'\"\'\"\'}"'"
6 answers
Una forma ligeramente mejor, evitando las posibles implicaciones de seguridad de usar eval
, es
declare "$var=$val"
Tenga en cuenta que declare
es un sinónimo de {[4] {} en[5]}. El comando typeset
es más ampliamente soportado (ksh
y zsh
también lo usan):
typeset "$var=$val"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-21 16:57:30
Bash tiene una extensión a printf
que guarda su resultado en una variable:
printf -v "${VARNAME}" '%s' "${VALUE}"
Esto evita todos los posibles problemas de escape.
Si utiliza un identificador no vá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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-07 06:45:03
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 algún comportamiento no deseado en casos de borde, como nombres de archivo con caracteres especiales.
Cuando el argumento a eval
es expandido por el shell, el $var
es reemplazado por el nombre de la variable, y el \$
es reemplazado por un dólar simple. Por lo tanto, la cadena que se evalúa se convierte en:
varname=$value
Esto es exactamente lo que querer.
Normalmente todas las expresiones de la forma $varname
deben estar entre comillas dobles. Solo hay dos lugares donde se pueden omitir las comillas: asignaciones variables y case
. Dado que esta es una asignación variable, las comillas no son necesarias aquí. Sin embargo, no hacen daño, por lo que también podría escribir el código original como:
eval "$var=\"the value is $val\""
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-02 17:42:22
El punto principal es que la forma recomendada de hacer esto es:
eval "$var=\$val"
Con el RHS hecho indirectamente también. Dado que eval
se utiliza en el mismo
medio ambiente, tendrá $val
enlazado, por lo que el aplazamiento funciona, y desde
ahora es sólo una variable. Dado que la variable $val
tiene un nombre conocido,
no hay problemas con citar, e incluso podría haber sido escrito como:
eval $var=\$val
Pero como es mejor añadir siempre comillas, lo primero es mejor, o incluso esto:
eval "$var=\"\$val\""
A mejor alternativa en bash que se mencionó para toda la cosa que
evita eval
completamente (y no es tan sutil como declare
, etc):
printf -v "$var" "%s" "$val"
Aunque esta no es una respuesta directa a lo que pregunté originalmente...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-02 20:08:22
Las versiones más recientes de bash soportan algo llamado "transformación de parámetros", documentado en una sección del mismo nombre en bash(1).
"${value@Q}"
se expande a una versión de "${value}"
entre comillas de shell que puede reutilizar como entrada.
Lo que significa que lo siguiente es una solución segura:
eval="${varname}=${value@Q}"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-30 16:17:24
Solo para completar, también quiero sugerir el posible uso del bash incorporado en read.
Pero se debe tener mucho cuidado al usar read para asegurar que la entrada esté desinfectada (-d\0 lee hasta la terminación nula e printf "...\0 " termina el valor con un null), y esa lectura se ejecuta en el shell principal donde se necesita la variable y no una subcapa (de ahí el
var=x; val=foo
read -d\0 -r $var < <(printf "$val\0")
echo $x # --> foo
echo ${!var} # --> foo
Probé esto con espacios \n \t \r, etc. funcionó como se esperaba en mi versión de bash.
La-r evitará el escape \, por lo que si tiene los caracteres "\" y "n" en su valor y no una nueva línea real, x también contendrá los dos caracteres "\" y "n".
Este método puede no ser estéticamente tan agradable como la solución eval o printf, y sería más útil si el valor viene de un archivo u otro descriptor de archivo de entrada
read -d\0 -r $var < <( cat $file )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-21 12:25:34