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?
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?
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.
eval
in the first place to this answer too. Stuff like indirectly modifying variables can be done in many shells through declare
/typeset
/nameref
and expansions like ${!var}
, so I would use those instead of eval
unless I really had to avoid it.
– ilkkachu
Nov 14 '17 at 18:02
eval
and $(...)
?
– Charlie Parker
Jun 08 '22 at 14:16
$(...)
is a yet another thing, different from eval
and exec
. Of course that one, also, runs a command...
– ilkkachu
Jun 08 '22 at 15:04
$(...)
and exec
? Sorry trying to figure out what is going on. All these three (exec
, eval
, $(...)
) things seem very similar and I can't figure out the difference. For context I am going through https://unix.stackexchange.com/questions/23111/what-is-the-eval-command-in-bash and trying to understand the command eval $(opam env)
.
– Charlie Parker
Jun 08 '22 at 15:07
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
.
eval
and $(...)
?
– Charlie Parker
Jun 08 '22 at 14:16
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.
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;
{
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
creating a new child process, run the arguments and returning the exit status.
Uh what? The whole point of eval
is that it does not in any way create a child process. If I do
eval "cd /tmp"
in a shell, then afterwards the current shell will have changed directory. Neither does exec
create a new child process, instead it changes the current executable (namely the shell) for the given one; the process id (and open files and other stuff) stay the same. As opposed to eval
, an exec
will not return to the calling shell unless the exec
itself fails due to being unable to find or load the executable or die to argument expansion problems.
eval
basically interprets its argument(s) as a string after concatenation, namely it will do an extra layer of wildcard expansion and argument splitting. exec
doesn't do anything like that.
Evaluation
These work:
$ echo hi
hi
$ eval "echo hi"
hi
$ exec echo hi
hi
However, these do not:
$ exec "echo hi"
bash: exec: echo hi: not found
$ "echo hi"
bash: echo hi: command not found
Process image replacement
This example demonstrates how exec
replaces the image of its calling process:
# Get PID of current shell
sh$ echo $$
1234
# Enter a subshell with PID 5678
sh$ sh
# Check PID of subshell
sh-subshell$ echo $$
5678
# Run exec
sh-subshell$ exec echo $$
5678
# We are back in our original shell!
sh$ echo $$
1234
Notice that exec echo $$
ran with the PID of the subshell! Furthermore, after it was complete, we were back in our original sh$
shell.
On the other hand, eval
does not replace the process image. Rather, it runs the given command as you would normally within the shell itself. (Of course, if you run a command that requires a process to be spawned... it does just that!)
sh$ echo $$
1234
sh$ sh
sh-subshell$ echo $$
5678
sh-subshell$ eval echo $$
5678
# We are still in the subshell!
sh-subshell$ echo $$
5678
exec
)
– muru
Jul 29 '19 at 05:04
eval
and$(...)
? – Charlie Parker Jun 08 '22 at 14:16eval
,exec
and$()
are three different things. Here is an example of the difference betweeneval
and$()
https://stackoverflow.com/questions/30155960/what-is-the-use-of-eval-opam-config-env-or-eval-opam-env-and-their-differen – Charlie Parker Jun 08 '22 at 15:43