How a shell runs a command: fork, exec, source and friends
./script.sh, exec ./script.sh, . ./script.sh and ./script.sh & look
alike and do four different things. What differs each time is whether a new
process exists, whose environment changes, what $? holds afterwards, and
who gets the terminal’s signals.
The ways to run something
./script.sh- Fork, then exec. The child gets a copy of the environment, the parent
waits, and
$?is the child's exit code. The default, and the isolation it gives is usually what you want. exec ./app- No fork: the running process becomes
app, PID and open descriptors included. Nothing returns to the shell, so there is no exit code to read. This is how a wrapper script hands over to the real program without leaving a shell in the process tree. . ./script.sh, orsource- No new process: the lines run in the current shell, so variables,
functions and
cdstick.$?is the last command's. For profiles, function libraries and environment files. ./script.sh &- Fork without waiting.
$?is 0 as soon as the launch worked;waitgives the real status later. In an interactive shell the job lives in its own process group and keyboard signals do not reach it. nohup ./script.sh &- Background, plus immunity to
SIGHUPwhen the terminal goes, with stdin from/dev/nulland output tonohup.outunless redirected. (cd /tmp && ls)- A subshell: a forked copy of the shell, so the
cdand any assignment are gone when the parenthesis closes.$?is the group's last status. daemon -- ./app- A detached process reparented to init, with a clean environment and its descriptors closed. For a service started by hand; a service manager does the same with supervision.
Becoming another user
su user starts a shell as the user with most of your environment kept, only
HOME and SHELL changed. su - user starts a login shell: your environment
is discarded and the user’s profiles are read, which is what a service
account expects. su -c 'cmd' user runs one command in a non-interactive
shell with no profile read, so PATH is whatever su passes on.
Capturing output
$(command) runs the command in a subshell and substitutes its output; it
nests and it is POSIX. Backticks do the same with worse quoting, and are
legacy. <(command) is different: it starts the command in the background
and substitutes a file name that reads its output, so a program that wants a
file can read a pipe, as in diff <(sort a) <(sort b). Bash and Zsh only.
Descriptors and signals
- A forked child inherits every open descriptor that was not closed or marked
close-on-exec;
execkeeps them too, since the process is the same;sourceshares them outright.nohupanddaemonswap the standard three for/dev/nullor a file. - Signal dispositions survive a fork. An
execresets handled signals to their defaults and keeps ignored ones ignored, which is why a background job launched with job control still ignoresSIGINTafter it execs. - Foreground commands share the terminal’s process group and get Ctrl-C; background jobs sit in their own group and do not. A pipeline is one group.
What $? means
a && b- The first failure, or
b's status when both pass. a || b- The first success, or
b's status when both fail. a | b- The last command's, unless
set -o pipefail, which makes it the last failure in the pipe. ! aa's status, inverted.if a; thena's status picks the branch; theifitself exits with the branch's last command.