Bash – Difference Between eval and exec

bashshellshell-builtin

eval and exec are both built in commands of bash(1) that execute commands.

I also see exec has a few options but is that the only difference? What happens to their context?

Best Answer

eval and exec are completely different beasts. (Apart from the fact that both will run commands, but so does everything you do in a shell.)

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

What exec cmd does, is exactly the same as just running cmd, except that the current shell is replaced with the command, instead of a separate process being run. Internally, running say /bin/ls will call fork() to create a child process, and then exec() in the child to execute /bin/ls. exec /bin/ls on the other hand will not fork, but just replaces the shell.

Compare:

$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo

with

$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217

echo $$ prints the PID of the shell I started, and listing /proc/self gives us the PID of the ls that was ran from the shell. Usually, the process IDs are different, but with exec the shell and ls have the same process ID. Also, the command following exec didn't run, since the shell was replaced.


On the other hand:

$ help eval
eval: eval [arg ...]
    Execute arguments as a shell command.

eval will run the arguments as a command in the current shell. In other words eval foo bar is the same as just foo bar. But variables will be expanded before executing, so we can execute commands saved in shell variables:

$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo

It will not create a child process, so the variable is set in the current shell. (Of course eval /bin/ls will create a child process, the same way a plain old /bin/ls would.)

Or we could have a command that outputs shell commands. Running ssh-agent starts the agent in the background, and outputs a bunch of variable assignments, which could be set in the current shell and used by child processes (the ssh commands you would run). Hence ssh-agent can be started with:

eval $(ssh-agent)

And the current shell will get the variables for other commands to inherit.


Of course, if the variable cmd happened to contain something like rm -rf $HOME, then running eval "$cmd" would not be something you'd want to do. Even things like command substitutions inside the string would be processed, so one should really be sure that the input to eval is safe before using it.

Often, it's possible to avoid eval and avoid even accidentally mixing code and data in the wrong way.

Related Question