50

I was reading a bash script someone made and I noticed that the author doesn't use eval to evaluate a variable as a command
The author used

bash -c "$1" 

instead of

eval "$1" 

I assume using eval is the preferred method and it's probably faster anyway. Is that true?
Is there any practical difference between the two? What are notable differences between the two?

1
  • 1
    In some occasions, you can get away without either. e='echo foo'; $e works just fine.
    – Dennis
    CommentedApr 14, 2014 at 12:45

3 Answers 3

47

eval "$1" executes the command in the current script. It can set and use shell variables from the current script, set environment variables for the current script, set and use functions from the current script, set the current directory, umask, limits and other attributes for the current script, and so on. bash -c "$1" executes the command in a completely separate script, which inherits environment variables, file descriptors and other process environment (but does not transmit any change back) but does not inherit internal shell settings (shell variables, functions, options, traps, etc.).

There is another way, (eval "$1"), which executes the command in a subshell: it inherits everything from the calling script but does not transmit any change back.

For example, assuming that the variable dir isn't exported and $1 is cd "$foo"; ls, then:

  • cd /starting/directory; foo=/somewhere/else; eval "$1"; pwd lists the content of /somewhere/else and prints /somewhere/else.
  • cd /starting/directory; foo=/somewhere/else; (eval "$1"); pwd lists the content of /somewhere/else and prints /starting/directory.
  • cd /starting/directory; foo=/somewhere/else; bash -c "$1"; pwd lists the content of /starting/directory (because cd "" doesn't change the current directory) and prints /starting/directory.
6
  • Thanks. I didn't know about (eval "$1"), is it any different from source?
    – biepbiep
    CommentedApr 13, 2014 at 23:06
  • 1
    @whoami (eval "$1") has nothing to do with source. It's just a combination of (…) and eval. source foo is roughly equivalent to eval "$(cat foo)".CommentedApr 13, 2014 at 23:10
  • We must have been writing our answers at the same time...
    – mikeserv
    CommentedApr 13, 2014 at 23:10
  • @whoami The primary difference between eval and .dot is that eval works with arguments and .dot works with files.
    – mikeserv
    CommentedApr 13, 2014 at 23:13
  • Thanks both of you. My previous comment appears to be kinda stupid now that I read it again...
    – biepbiep
    CommentedApr 13, 2014 at 23:29
28

The most important difference between

bash -c "$1" 

And

eval "$1" 

Is that the former runs in a subshell and the latter does not. So:

set -- 'var=something' bash -c "$1" echo "$var" 

OUTPUT:

#there doesn't seem to be anything here 
set -- 'var=something' eval "$1" echo "$var" 

OUTPUT:

something 

I have no idea why anyone would ever use the executable bash in that way, though. If you must invoke it, use the POSIX guaranteed built-in sh. Or (subshell eval) if you wish to protect your environment.

Personally, I prefer the shell's .dot above all.

printf 'var=something%d ; echo "$var"\n' `seq 1 5` | . /dev/fd/0 

OUTPUT

something1 something2 something3 something4 something5 

BUT DO YOU NEED IT AT ALL?

The only cause to use either, really, is in the event that your variable actually assigns or evaluates another, or word-splitting is important to the output.

For instance:

var='echo this is var' ; $var 

OUTPUT:

this is var 

That works, but only because echo doesn't care about its argument count.

var='echo "this is var"' ; $var 

OUTPUT:

"this is var" 

See? The double-quotes come along because the result of the shell's expansion of $var is not evaluated for quote-removal.

var='printf %s\\n "this is var"' ; $var 

OUTPUT:

"this is var" 

But with eval or sh:

 var='echo "this is var"' ; eval "$var" ; sh -c "$var" 

OUTPUT:

this is var this is var 

When we use eval or sh the shell takes a second pass at the results of the expansions and evaluates them as a potential command as well, and so the quotes make a difference. You can also do:

. <<VAR /dev/fd/0 ${var:=echo "this is var"} #END VAR 

OUTPUT

this is var 
    5

    I did a quick test:

    time bash -c 'for i in {1..10000}; do bash -c "/bin/echo hi"; done' time bash -c 'for i in {1..10000}; eval "/bin/echo hi"; done' 

    (Yes, I know, I used bash -c to execute the loop but that should not make a difference).

    The results:

    eval : 1.17s bash -c : 7.15s 

    So eval is faster. From the man page of eval:

    The eval utility shall construct a command by concatenating arguments together, separating each with a character. The constructed command shall be read and executed by the shell.

    bash -c of course, executes the command in a bash shell. One note: I used /bin/echo because echo is a shell built-in with bash, meaning a new process does not need to be started. Replacing /bin/echo with echo for the bash -c test, it took 1.28s. That is about the same. Hovever, eval is faster for running executables. The key difference here is that eval does not start a new shell (it executes the command in the current one) whereas bash -c starts a new shell, then executes the command in the new shell. Starting a new shell takes time, and that is why bash -c is slower than eval.

    4
    • I think the OP wants to compare bash -c with eval not exec.
      – Joseph R.
      CommentedApr 13, 2014 at 22:29
    • @JosephR. Oops! I'll change that.CommentedApr 13, 2014 at 22:29
    • 1
      @JosephR. It should be fixed now. Also I redid the tests a bit more and bash -c isn't that bad...CommentedApr 13, 2014 at 22:35
    • 3
      While this is true, it misses the fundamental difference that the command is executed in different environments. It's obvious that starting a new instance of bash will be slower, this is not an interesting observation.CommentedApr 13, 2014 at 22:48

    You must log in to answer this question.

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.