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
hello
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
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
hello
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
exit
$ cat output.log
hello
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
goodbye
$ cat output.log
hello
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:
#!/bin/bash
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
PID TTY TIME CMD
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:
- cmd_name: holds the command that we want to run
- 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:
- different syntax
- redirections in exec persist in the current shell session
- exec substitutes our current shell session
- eval parses its input before executing it
In conclusion, while both commands serve a similar purpose, subtle differences exist between them.