Home » What’s the difference between eval and exec?

What’s the difference between eval and exec?

Solutons:


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.

exec does not create a new process. It replaces the current process with the new command. If you did this on the command line then it will effectively end your shell session (and maybe log you out or close the terminal window!)

e.g.

ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh% 

Here I’m in ksh (my normal shell). I start bash and then inside bash I exec /bin/echo. We can see that I’ve been dropped back into ksh afterwards because the bash process was replace by /bin/echo.

TL;DR

exec is used to replace current shell process with new and handle stream redirection/file descriptors if no command has been specified. eval is used to evaluate strings as commands. Both may be used to built up and execute a command with arguments known at run-time, but exec replaces process of the current shell in addition to executing commands.

exec buil-in

Syntax:

exec [-cl] [-a name] [command [arguments]]

According to the manual if there is command specified this built-in

…replaces the shell. No new process is created. The arguments become the arguments to command.

In other words, if you were running bash with PID 1234 and if you were to run exec top -u root within that shell, the top command will then have PID 1234 and replace your shell process.

Where is this useful ? In something known as wrapper scripts. Such scripts build up sets of arguments or make certain decisions about what variables to pass into environment, and then use exec to replace itself with whatever command is specified, and of course providing those same arguments that the wrapper script has built up along the way.

What the manual also states is that:

If command is not specified, any redirections take effect in the current shell

This allows us to redirect anything from current shells output streams into a file. This may be useful for logging or filtering purposes, where you don’t want to see stdout of commands but only stderr. For instance, like so:

bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt 
2017年 05月 20日 星期六 05:01:51 MDT
HELLO WORLD

This behavior makes it handy for logging in shell scripts, redirecting streams to separate files or processes, and other fun stuff with file descriptors.

On the source code level at least for bash version 4.3, the exec built in is defined in builtins/exec.def. It parses the received commands, and if there are any, it passes things on to shell_execve() function defined in execute_cmd.c file.

Long story short, there exists a family of exec commands in C programming language, and shell_execve() is basically a wrapper function of execve:

/* Call execve (), handling interpreting shell scripts, and handling
   exec failures. */
int
shell_execve (command, args, env)
     char *command;
     char **args, **env;
{

eval built-in

The bash 4.3 manual states(emphasis added by me):

The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval.

Note that there is no process replacement occurring. Unlike exec where the goal is to simulate execve() functionality, the eval built in only serves to “evaluate” arguments, just as if the user has typed them on the command line. As such, new processes are created.

Where this might be useful ? As Gilles pointed out in this answer , “…eval is not used very often. In some shells, the most common use is to obtain the value of a variable whose name is not known until runtime”. Personally, I’ve used it in couple of scripts on Ubuntu where it was necessary to execute/evaluate a command based on the specific workspace that the user was currently using.

On the source code level, it is defined in builtins/eval.def and passes the parsed input string to evalstring() function.

Among other things, eval can assign variables which remain in current shell execution environment, while exec cannot:

$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...