Both exec
and env
don't fork, see the following example:
docker run --rm -it ubuntu:18.04 sh -c 'exec sleep 1 & ps -Ho pid,ppid,cmd'
PID PPID CMD
1 0 sh -c exec sleep 1 & ps -Ho pid,ppid,cmd
7 1 sleep 1
8 1 ps -Ho pid,ppid,cmd
docker run --rm -it ubuntu:18.04 sh -c 'env sleep 1 & ps -Ho pid,ppid,cmd'
PID PPID CMD
1 0 sh -c env sleep 1 & ps -Ho pid,ppid,cmd
7 1 sleep 1
8 1 ps -Ho pid,ppid,cmd
I see lots of people using exec env ...
but I don't think exec
is necessary because env
doesn't fork just like exec
.
Is there any use case that we need to use exec env
instead of env
?
env
(probably) doesn't itself fork, just replaces itself with the program it does. But that's different from the parent shell forking off env
, or just replacing itself with env
.
E.g. compare these two:
$ bash -c 'env ls > /dev/null; echo hi'
hi
$ bash -c 'exec env ls > /dev/null; echo hi'
[no output]
With exec
, the shell itself is replaced, and the following echo
doesn't run.
Of course, you had exec env ... &
instead. With &
, the shell forks anyway to start the background process, and it might not fork again to run the single command within the background task. At least Bash optimizes cases like that, e.g.:
$ bash -c 'ps -Ho pid,ppid,cmd; echo x'
PID PPID CMD
11967 1545 -/bin/bash
14851 11967 bash -c ps -Ho pid,ppid,cmd; echo x
14852 14851 ps -Ho pid,ppid,cmd
x
$ bash -c 'ps -Ho pid,ppid,cmd'
PID PPID CMD
11967 1545 -/bin/bash
14853 11967 ps -Ho pid,ppid,cmd
where the latter doesn't show the in-between bash
process, the behaviour is the same as if there was an exec
there. (-/bin/bash
is my interactive shell.)
Bash only does that for an alone command, if there are more than one, the shell waits for the last one to exit. Using exec
there would make a difference:
$ bash -c 'echo x; ps -Ho pid,ppid,cmd'
x
PID PPID CMD
11967 1545 -/bin/bash
14929 11967 bash -c echo x; ps -Ho pid,ppid,cmd
14930 14929 ps -Ho pid,ppid,cmd
$ bash -c 'echo x; exec ps -Ho pid,ppid,cmd'
x
PID PPID CMD
11967 1545 -/bin/bash
14933 11967 ps -Ho pid,ppid,cmd
With &
, this would be the same with exec ps
:
$ bash -c 'ps -Ho pid,ppid,cmd & sleep 1'
PID PPID CMD
11967 1545 -/bin/bash
14867 11967 bash -c ps -Ho pid,ppid,cmd & sleep 1
14868 14867 ps -Ho pid,ppid,cmd
14869 14867 sleep 1
But with a compound block, we see the background shell process too:
$ bash -c '{ ps -Ho pid,ppid,cmd; } & sleep 1'
PID PPID CMD
11967 1545 -/bin/bash
14877 11967 bash -c { ps -Ho pid,ppid,cmd; } & sleep 1
14878 14877 bash -c { ps -Ho pid,ppid,cmd; } & sleep 1
14880 14878 ps -Ho pid,ppid,cmd
14879 14877 sleep 1
You didn't give any specific sources for the those lots of people doing exec env
, so we don't know their exact situation. But you'd use exec env
the same way as exec anyprogram
: if you want to replace the shell with some other process. E.g. this mock example:
#!/bin/bash
. someconfigfile
if [ "$someconfig" = blah ]; then
exec env myprogram some args...
echo "failed to start" >&2
exit 1
elif
... whatever
fi
The exec
there replaces the script with the launched program, so that the shell doesn't remain in memory uselessly. Also if the script there is just an intermediary to start the program, doing an exec
will keep the PID the same, which makes it easier for the parent (of the script and then the program) to monitor its child.