19

I want to dynamically assign values to variables using eval. The following dummy example works:

var_name="fruit"
var_value="orange"
eval $(echo $var_name=$var_value)
echo $fruit
orange

However, when the variable value contains spaces, eval returns an error, even if $var_value is put between double quotes:

var_name="fruit"
var_value="blue orange"
eval $(echo $var_name="$var_value")
bash: orange : command not found

Any way to circumvent this ?

13

Don't use eval, use declare

$ declare "$var_name=$var_value"
$ echo "fruit: >$fruit<"
fruit: >blue orange<
11

Don't use eval for this; use declare.

var_name="fruit"
var_value="blue orange"
declare "$var_name=$var_value"

Note that word-splitting is not an issue, because everything following the = is treated as the value by declare, not just the first word.

In bash 4.3, named references make this a little simpler.

$ declare -n var_name=fruit
$ var_name="blue orange"
$ echo $fruit
blue orange

You can make eval work, but you still shouldn't :) Using eval is a bad habit to get into.

$ eval "$(printf "%q=%q" "$var_name" "$var_value")"
  • 2
    Using eval that way is wrong. You're expanding $var_value before passing it to eval which means it's going to be interpreted as shell code! (try for instance with var_value="';:(){ :|:&};:'") – Stéphane Chazelas Sep 11 '14 at 21:57
  • 1
    Good point; there are some strings you can't safely assign using eval (which is one reason I said you shouldn't use eval). – chepner Sep 11 '14 at 21:59
  • @chepner - I do not believe that is true. maybe it is, but not this one at least. parameter substitutions allow for conditional expansion, and so you can expand only safe values in most cases, I think. still, your primary problem for $var_value is one of quote inversion - assuming a safe value for $var_name (which can be just as dangerous an assumption, really), then you should be enclosing the right-hand-side's double-quotes within single-quotes - not vice-versa. – mikeserv Sep 12 '14 at 8:51
  • I think I've fixed the eval, using printf and its bash-specific %q format. This is still not a recommendation to use eval, but I think it is safer than it was before. That fact that you need to go to this much effort to get it to work is proof that you should be using declare or named references instead. – chepner Sep 12 '14 at 12:00
  • Well, actually, in my opinion, named references are the problem. The best way to use it - in my experience - is like... set -- a bunch of args; eval "process2 $(process1 "$@")" where process1 just prints quoted numbers like "${1}" "${8}" "${138}". That's crazy simple - and as easy as '"${'$((i=$i+1))'}" ' in most cases. indexed references make it safe, robust, and fast. Still - I upvoted. – mikeserv Sep 12 '14 at 13:06
4

A good way to work with eval is to replace it with echo for testing. echo and eval work the same (if we set aside the \x expansion done by some echo implementations like bash's under some conditions).

Both commands join their arguments with one space in between. The difference is that echo displays the result while eval evaluates/interprets as shell code the result.

So, to see what shell code

eval $(echo $var_name=$var_value)

would evaluate, you can run:

$ echo $(echo $var_name=$var_value)
fruit=blue orange

That's not what you want, what you want is:

fruit=$var_value

Also, using $(echo ...) here doesn't make sense.

To output the above, you'd run:

$ echo "$var_name=\$var_value"
fruit=$var_value

So, to interpret it, that's simply:

eval "$var_name=\$var_value"

Note that it can also be used to set individual array elements:

var_name='myarray[23]'
var_value='something'
eval "$var_name=\$var_value"

As others have said, if you don't care your code being bash specific, you can use declare as:

declare "$var_name=$var_value"

However note that it has some side effects.

It limits the scope of the variable to the function where it's run in. So you can't use it for instance in things like:

setvar() {
  var_name=$1 var_value=$2
  declare "$var_name=$var_value"
}
setvar foo bar

Because that would declare a foo variable local to setvar so would be useless.

bash-4.2 added a -g option for declare to declare a global variable, but that's not what we want either as our setvar would set a global var as opposed to that of the caller if the caller was a function, like in:

setvar() {
  var_name=$1 var_value=$2
  declare -g "$var_name=$var_value"
}
foo() {
  local myvar
  setvar myvar 'some value'
  echo "1: $myvar"
}
foo
echo "2: $myvar"

which would output:

1:
2: some value

Also, note that while declare is called declare (actually bash borrowed the concept from the Korn shell's typeset builtin), if the variable is already set, declare doesn't declare a new variable and the way the assignment is done depends on the type of the variable.

For instance:

varname=foo
varvalue='([PATH=1000]=something)'
declare "$varname=$varvalue"

will produce a different result (and potentially have nasty side effects) if varname was previously declared as a scalar, array or associative array.

  • 2
    What's wrong with being bash specific? OP put the bash tag on the question, so he's using bash. Providing alternates is good, but I think telling someone not to use a feature of a shell because it's not portable is silly. – Patrick Sep 12 '14 at 0:38
  • @Patrick, seen the smiley? Having said that, using portable syntax means less effort when you need to port your code to another system where bash is not available (or when you realise you need a better/faster shell). The eval syntax works in all Bourne-like shells and is POSIX so all systems will have a sh where that works. (that also means my answer applies to all shells, and sooner or later, as happens often here, you'll see a non-bash specific question closed as a duplicate of this one. – Stéphane Chazelas Sep 12 '14 at 7:08
  • but what if $var_name contains tokens? ...like ;? – mikeserv Sep 12 '14 at 8:00
  • @mikeserv, then it's not a variable name. If you can't trust its content, then you need to sanitize it with both eval and declare (think of PATH, TMOUT, PS4, SECONDS...). – Stéphane Chazelas Sep 12 '14 at 8:47
  • but on the first pass its always a variable expansion and never a variable name until the second. in my answer i sanitize it with a parameter expansion, but if youre implying doing the sanitization in a subshell on the first pass, that could as well be done portably w/ export. i dont follow the parenthetical bit on the end though. – mikeserv Sep 12 '14 at 9:03
1

If you do:

eval "$name=\$val"

...and $name contains a ; - or any of several other tokens the shell might interpret as delimiting a simple command - preceded by proper shell syntax, that will be executed.

name='echo hi;varname' val='be careful with eval'
eval "$name=\$val" && echo "$varname"

OUTPUT

hi
be careful with eval

It can sometimes be possible to separate the evaluation and execution of such statements, though. For example, alias can be used to pre-evaluate a command. In the following example the variable definition is saved to an alias that can only be successfully declared if the $nm variable it is evaluating contains no bytes that do not match ASCII alphanumerics or _.

LC_OLD=$LC_ALL LC_ALL=C
alias "${nm##*[!_A-Z0-9a-z]*}=_$nm=\$val" &&
eval "${nm##[0-9]*}" && unalias "$nm"
LC_ALL=$LC_OLD

eval is used here to handle invoking the new alias from a varname. But it is only called at all if the previous alias definition is successful, and while I know a lot of different implementations will accept a lot of different kinds of values for alias names, I haven't yet run into one that will accept a completely empty one.

The definition within the alias is for _$nm, however, and this is to ensure that no significant environment values are written over. I don't know of any noteworthy environment values beginning with a _ and it is usually a safe bet for semi-private declaration.

Anyway, if the alias definition is successful it will declare an alias named for $nm's value. And eval will only call that alias if also does not start with a number - else eval gets only a null argument. So if both conditions are met eval calls the alias and the variable definition saved in the alias is made, after which the new alias is promptly removed from the hash table.

  • ; is not allowed in variable names. If you don't have control over the content of $name, then you need to sanitize it for export/declare as well. While export doesn't execute code, setting some variables like PATH, PS4 and many of those at info -f bash -n 'Bash Variables' have equally dangerous side effects. – Stéphane Chazelas Sep 12 '14 at 9:04
  • @StéphaneChazelas - of course it is not allowed, but, as before, its not a variable name on eval's first pass - it is a variable expansion. As you said elsewhere, in that context it is very much allowed. Still the $PATH argument is a very good one - i made a small edit and will add some later. – mikeserv Sep 12 '14 at 10:54
  • @StéphaneChazelas - better late than never...? – mikeserv Oct 28 '14 at 13:23
  • In practice, zsh, pdksh, mksh, yash don't complain on unset 'a;b'. – Stéphane Chazelas Oct 28 '14 at 13:30
  • You'll want unset -v -- ... as well. – Stéphane Chazelas Oct 28 '14 at 13:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.