Quiero asignar valores dinámicamente a variables usando eval. El siguiente ejemplo ficticio funciona:
var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange
Sin embargo, cuando el valor de la variable contiene espacios, evaldevuelve un error, incluso si $var_valuese coloca entre comillas dobles:
var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found
¿Alguna forma de evitar esto?

evalese camino está mal. ¡Se está expandiendo$var_valueantes de pasarlo, loevalque significa que se interpretará como código shell! (prueba por ejemplo convar_value="';:(){ :|:&};:'")eval(que es una razón por la que dije que no deberías usareval).$var_valuees el de la inversión de comillas, suponiendo un valor seguro para$var_name(que puede ser una suposición tan peligrosa, realmente) , entonces debería encerrar las comillas dobles del lado derecho dentro de comillas simples, no viceversa.eval, usandoprintfy su formatobashespecífico%q. Todavía no es una recomendación para usareval, pero creo que es más seguro de lo que era antes. El hecho de que tenga que hacer todo este esfuerzo para que funcione es una prueba de que debería usardeclarereferencias nombradas.set -- a bunch of args; eval "process2 $(process1 "$@")"dondeprocess1solo imprime números citados como"${1}" "${8}" "${138}". Eso es una locura simple, y tan fácil como'"${'$((i=$i+1))'}" 'en la mayoría de los casos. Las referencias indexadas lo hacen seguro, robusto y rápido . Aún así, he votado.Una buena forma de trabajar
evales reemplazarlo porechopara probar.echoyevalfunciona igual (si dejamos de lado la\xexpansión realizada por algunasechoimplementaciones comobashen algunas condiciones).Ambos comandos unen sus argumentos con un espacio en el medio. La diferencia es que
echomuestra el resultado mientrasevalevalúa / interpreta como código shell el resultado.Entonces, para ver qué código de shell
evaluaría, puede ejecutar:
Eso no es lo que quieres, lo que quieres es:
Además, usar
$(echo ...)aquí no tiene sentido.Para generar lo anterior, ejecutarías:
Entonces, para interpretarlo, eso es simplemente:
Tenga en cuenta que también se puede usar para establecer elementos de matriz individuales:
Como otros han dicho, si no te importa que tu código sea
bashespecífico, puedes usarlodeclarecomo:Sin embargo, tenga en cuenta que tiene algunos efectos secundarios.
Limita el alcance de la variable a la función donde se ejecuta. Por lo tanto, no puede usarla, por ejemplo, en cosas como:
Porque eso declararía que una
foovariable localsetvarsería inútil.bash-4.2agregó una-gopción paradeclaredeclarar una variable global , pero eso tampoco es lo que queremos, yasetvarque estableceríamos una var global en lugar de la de la persona que llama si la persona que llama fue una función, como en:que daría salida:
Además, tenga en cuenta que mientras
declarese llamadeclare(en realidadbashtomó prestado el concepto de la construcción del shell Korntypeset), si la variable ya está establecida,declareno declara una nueva variable y la forma en que se realiza la asignación depende del tipo de la variable.Por ejemplo:
producirá un resultado diferente (y potencialmente tendrá efectos secundarios desagradables) si
varnamese declaró previamente como un escalar , matriz o matriz asociativa .fuente
bashno está disponible (o cuando se da cuenta de que necesita un shell mejor / más rápido). Laevalsintaxis funciona en todos los shells tipo Bourne y es POSIX, por lo que todos los sistemas tendrán un lugarshdonde eso funcione. (eso también significa que mi respuesta se aplica a todos los shells, y tarde o temprano, como sucede a menudo aquí, verás una pregunta específica que no es bash cerrada como un duplicado de esta.)$var_namecontiene tokens? ... como;?evalydeclare(pensarPATH,TMOUT,PS4,SECONDS...).export. Sin embargo, no sigo el bit entre paréntesis al final.Si lo haces:
... y
$namecontiene una;, o cualquiera de varias otras señales que el shell podría interpretar como delimitación de un comando simple, precedido por la sintaxis adecuada del shell, que se ejecutará.SALIDA
Sin embargo, a veces puede ser posible separar la evaluación y la ejecución de tales declaraciones. Por ejemplo,
aliasse puede usar para evaluar previamente un comando. En el siguiente ejemplo, la definición de la variable se guarda en unaaliasque solo puede declararse con éxito si la$nmvariable que está evaluando no contiene bytes que no coincidan con los caracteres alfanuméricos o ASCII_.evalse usa aquí para manejar la invocación de lo nuevoaliasdesde un nombre var. Pero solo se llama si laaliasdefinición anterior es exitosa, y aunque sé que muchas implementaciones diferentes aceptarán muchos tipos diferentes de valores para losaliasnombres, todavía no me he encontrado con uno que acepte uno completamente vacío. .Sin embargo, la definición dentro de
aliases para_$nm, y esto es para garantizar que no se sobrescriban valores de entorno significativos. No conozco ningún valor de entorno notable que comience con a_y, por lo general, es una apuesta segura para la declaración semiprivada.De todos modos, si la
aliasdefinición es exitosa, declarará unaliasnombre para$nmel valor de. Yevalsolo llamará a esoaliassi tampoco comienza con un número; de lo contrario,evalsolo obtiene un argumento nulo. Entonces, si se cumplen ambas condiciones, seevalllama alias y se realiza la definición de la variable guardada en el alias, después de lo cual la nuevaaliasse elimina rápidamente de la tabla hash.fuente
;no está permitido en nombres de variables. Si no tiene control sobre el contenido de$name, entonces necesita desinfectarlo paraexport/declaretambién. Mientrasexportque no ejecuta código, establece algunas variables comoPATH,PS4y muchas de ellasinfo -f bash -n 'Bash Variables'tienen efectos secundarios igualmente peligrosos.evalel primer paso, es una expansión variable. Como dijiste en otra parte, en ese contexto está muy permitido. Aún así, el argumento $ PATH es muy bueno: hice una pequeña edición y la agregaré más adelante.zsh,pdksh,mksh,yashno se quejan deunset 'a;b'.unset -v -- ....