Unix & Linux
environment-variables ps exec
Updated Tue, 16 Aug 2022 18:38:33 GMT

`exec env` vs `env`


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

Question

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?




Solution

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.