No, the subshell was created first.
A shell execution environment contains shell parameters set by variable assignments and environment variables. A subshell environment was created by duplicating the shell environment, so it contains all the variables of the current shell environment.
See the example:
$ b=1
$ c=$(b=2; echo "$b")
$ echo "$c"
2
The output is 2
instead of 1
.
A subshell environment created by command substitution is different with a shell environment created by calling the shell executable.
When you call the shell as:
$ bash -c :
the the current shell used execve() to create new shell process, something like:
execve("/bin/bash", ["bash", "-c", ":"], [/* 64 vars */]) = 0
the last argument passed to execve
contains all the environment variables.
That's why you need to export the variables to push it to the environment variables, which will be included in subsequently executed commands:
$ a=; export a
$ strace -e execve bash -c :
execve("/bin/bash", ["bash", "-c", ":"], [/* 65 vars */]) = 0
+++ exited with 0 +++
Notice the environment variables change from 64 to 65. And variables which are not exported will not be passed to new shell environment:
$ a=; b=; export a
$ strace -e execve bash -c :
execve("/bin/bash", ["bash", "-c", ":"], [/* 65 vars */]) = 0
+++ exited with 0 +++
Notice the environment variables are still 65.
In command substitution, the shell used fork() to create new shell process, which just copied the current shell environment - which contains both variables set and environment variables.
exec
is often used in shell scripts which mainly act as wrappers for starting other binaries. For example:
#!/bin/sh
if stuff;
EXTRA_OPTIONS="-x -y -z"
else
EXTRA_OPTIONS="-a foo"
fi
exec /usr/local/bin/the.real.binary $EXTRA_OPTIONS "$@"
so that after the wrapper is finished running, the "real" binary takes over and there is no longer any trace of the wrapper script that temporarily occupied the same slot in the process table. The "real" binary is a direct child of whatever launched it instead of a grandchild.
You mention also I/O redirection in your question. That is quite a different use case of exec
and has nothing to do with replacing the shell with another process. When exec
has no arguments, like so:
exec 3>>/tmp/logfile
then I/O redirections on the command line take effect in the current shell process, but the current shell process keeps running and moves on to the next command in the script.
Best Answer
Assuming that the script is a Bash script (i.e. it begins with
#!/bin/bash
), then the author is wrong on this point. When you run a shell script directly, the kernel sees the#!/bin/bash
line at the beginning, then runs that line with the script's filename as a parameter, i.e./bin/bash myscript.sh
. In other words, the kernel is doing exactly what you would do to pass it to a shell explicitly.Now, if the script begins with
#!/bin/sh
instead of#!/bin/bash
, then there's another possibility. Bash can provide/bin/sh
, but it doesn't have to. It also prioritizes features over size or speed. If a smaller, faster shell is your/bin/sh
, then running the script directly will use that faster shell, and the script will run faster than it would under Bash.This happens in practice with Debian-based distributions (e.g. Debian, Ubuntu, Mint). They tend to use a shell called
dash
to provide/bin/sh
instead of Bash, in order to make the boot scripts run faster.To see what your distribution does,
ls -l /bin/sh
. It is probably a symbolic link to/bin/bash
or/bin/dash
, or another shell.