1. Overview

Both eval and exec are shell built-in commands. The eval built-in command combines its arguments into a single string, evaluates it, and passes the result to the shell for execution. The exec command, on the other hand, replaces the current shell session to execute its input command.

In this tutorial, we’ll explore the differences between eval and exec.

2. Syntax

First, let’s explore the differences in the syntax of eval and exec.

The eval command accepts an arbitrary number of input arguments wherein these arguments are combined into a single string. This string is then evaluated and submitted to the shell for execution. Consequently, we can enter any number of commands along with their options and arguments:

$ eval date;ps;echo hello
Sat Sep  2 11:57:36 EEST 2023
  PID TTY          TIME CMD
  175 pts/0    00:00:00 bash
  199 pts/0    00:00:00 ps

Here, we can see that we entered three commands to eval, which executed them sequentially.

The exec command, on the other hand, has a different syntax. We can set a single command along with its arguments for execution. Furthermore, we can define redirections:

$ exec echo hello

Indeed, we can see that the exec command ran the echo command and printed the hello string to the stdout. Let’s enter two commands to see what happens:

$ exec date;echo hello
Sat Sep  2 12:22:49 EEST 2023

In the above example, we entered the date and echo commands for sequential execution. Furthermore, we can see that the date command printed the current date to stdout, while echo didn’t run at all.

3. Redirections

Another difference between the two commands concerns redirections. The exec command performs redirections that take effect in the current shell:

$ bash -c 'exec echo hello 1>output.log'
$ cat output.log

In this example, we redirected standard output to the output.log file. As a result, the echo command printed the hello string value to the output.log file instead of stdout.

Furthermore, we can supply a redirection without a command:

$ exec 1>output.log
$ echo hello
$ date
$ exit
$ cat output.log
Sat Sep 16 12:26:57 EEST 2023

Here, we redirected stdout to output.log using exec. An important point here is that this redirection persists in the current shell. As a result, the subsequent echo command prints the hello string to the output.log file. The same stands for the date command, which outputs the current date to output.log, instead of stdout.

In order to print output.log to stdout, we use cat after we exit the current shell session. Indeed, output.log contains the hello string and the current date.

The eval command can handle redirections, too. But, contrary to exec, redirections don’t persist in the current shell session:

$ eval echo hello 1>output.log
$ echo goodbye 
$ cat output.log 

Indeed, we can see the hello string isn’t printed in the terminal because we redirected stdout to output.log. However, the subsequent echo command outputs to the standard output because the redirection no longer applies.

4. Shell Substitution

The exec command replaces the current shell session to execute the entered command. To demonstrate it, let’s create a shell script that prints the PID of the running process:

ps | grep subshell

Let’s save the script to a file named subshell.sh. The ps command prints the running processes. In addition, grep keeps only the line that describes the process running the subshell.sh script. Let’s run the script with exec:

$ ps
9 pts/0 00:00:00 bash
20 pts/0 00:00:00 ps
$ exec ./subshell.sh
    9 pts/0    00:00:00 subshell

In this example, first, we print the running processes where we can see that the current shell session has a PID of 9. Then, we run the subshell script with exec. We can observe that the shell script has taken the place of Bash. Furthermore, after the execution of the exec command, our current shell session is terminated.

The eval command, on the other hand, doesn’t replace the current shell session. Instead, it creates a subshell:

$ ps
  PID TTY          TIME CMD
    9 pts/0    00:00:00 bash
   21 pts/0    00:00:00 ps
$ eval ./subshell.sh
   31 pts/0    00:00:00 subshell

Here, we can see that the current shell session has a PID of 9, while the subshell’s process has a PID of 31.

5. Argument Evaluation

Another difference between eval and exec is that eval parses its input parameters before it submits them to the shell for execution. To demonstrate a scenario, let’s create a small template expression:

$ command_line='$cmd_name $opts'

Here, we assign the command_line shell variable a string value that contains two other shell variables:

  1. cmd_name: holds the command that we want to run
  2. opts: contains the options of the command

Our objective is to assign values to cmd_name and opts so that we can dynamically execute commands with their options. Furthermore, we used single quotes that don’t support variable interpolation, in contrast to double quotes.

Next, let’s start assigning values to the cmd_name and opts shell variables:

$ cmd_name=date
$ opts='-d 01/01/2000'

Here, we want to execute the command date -d 01/01/2000. Next, let’s use eval to evaluate our expression:

$ eval $command_line
Sat Jan  1 00:00:00 EET 2000

Indeed, the eval command successfully evaluated and substituted the $cmd_name and $opts shell variables. The resulting string was ‘date -d 01/01/2000’. This string was then submitted to the shell for execution. Finally, the result was printed to the standard output.

Next, let’s evaluate and run the expression with the exec command:

$ exec $command_line
-bash: exec: $cmd_name: not found

As we expected, the use of exec didn’t lead to the evaluation of the expression. The $command_line variable is expanded to ‘$command_name $opts’. Since exec doesn’t perform any variable interpolation, it executes the expression as it is and fails.

6. Conclusion

In this article, we examined the differences between the exec and eval shell built-in commands. To summarize, the differences are:

  1. different syntax
  2. redirections in exec persist in the current shell session
  3. exec substitutes our current shell session
  4. eval parses its input before executing it

In conclusion, while both commands serve a similar purpose, subtle differences exist between them.