Shell builtins · bash builtin
exec
Replace the shell with another program, or, with only redirections, change where the shell's own input and output go.
exec [-cl] [-a NAME] [COMMAND [ARGUMENT...]] [REDIRECTION...]
exec has two distinct uses. Given a command, it replaces the current shell process with
that program: same PID, no fork, and nothing after the exec line ever runs, because the
shell is gone. Wrapper scripts and container entrypoints end with exec "$@" or exec
program so the real program receives signals directly and no idle shell is left waiting.
Given only redirections, exec runs nothing and instead applies them to the current shell
permanently. exec >log 2>&1 sends everything the script prints from then on into a file;
exec 3<file opens file descriptor 3 for reading so a loop can use it; exec 3>&- closes
it. This is the cleanest way to log a whole script without wrapping every command.
If the program cannot be found, a non-interactive shell exits (status 127) rather than
carrying on, unless the execfail shell option is set.
Examples
Replace the shell with another program
echo "shell pid: $$"
exec bash -c 'echo "new program pid: $$"'
echo "this line never runs"
shell pid: 728 new program pid: 728
The PID stays the same: exec did not start a child, it turned the current process into
the new program. When that program exits, there is no shell to return to, so the last
echo never runs.
End a wrapper script with exec
#!/usr/bin/env bash
# set up the environment, then hand over to the real program
export GREETING_STYLE=formal
cd "$(dirname "$0")/scripts" || exit 1
exec ./greet.sh "$@"
bash run-greet.sh Alice
echo "wrapper exit status: $?"
Hello, Alice! wrapper exit status: 0
The wrapper prepares the environment and directory, then exec replaces itself with the
program, passing the arguments through with "$@". The exit status is the program's own.
In containers this makes the program PID 1 so it receives docker stop's signal.
Send the rest of a script's output to a log file
#!/usr/bin/env bash
echo "starting, details in run.log"
exec >run.log 2>&1
echo "step 1 done"
ls missing-file
echo "step 3 done"
bash job.sh
echo "--- run.log:"
cat run.log
starting, details in run.log --- run.log: step 1 done ls: cannot access 'missing-file': No such file or directory step 3 done
Only the first line reaches the terminal. After exec >run.log 2>&1, every later
command in the script writes both stdout and stderr to run.log, including the error
from ls. The redirection lasts until the script ends; the calling shell is not affected.
Redirect temporarily and restore the original output
exec 3>&1
exec >capture.txt
echo "written to the file"
exec 1>&3 3>&-
echo "back on the terminal"
echo "file contains: $(cat capture.txt)"
back on the terminal file contains: written to the file
exec 3>&1 saves the current stdout as descriptor 3. After redirecting stdout to the
file, exec 1>&3 3>&- restores it and closes the spare descriptor.
Log to a file and the terminal at the same time
exec > >(tee build.log) 2>&1
echo "compiling"
echo "warning: unused variable" >&2
echo "done"
sleep 0.2
echo "--- log has $(wc -l < build.log) lines"
compiling warning: unused variable done --- log has 3 lines
Process substitution starts tee, and exec points stdout and stderr at it, so output
still appears on screen and is also saved in build.log. tee runs asynchronously; the
short sleep lets it catch up before the log is counted.
Open a file on a descriptor and read from it
exec 3< data/hosts.txt
read -r first <&3
read -r second <&3
exec 3<&-
echo "1: $first"
echo "2: $second"
1: web01 10.0.0.11 2: web02 10.0.0.12
`exec 3
Keep a file open for appending
exec 4>>events.log
for i in 1 2 3; do echo "event $i" >&4; done
exec 4>&-
cat events.log
event 1 event 2 event 3
Opening the log once with 4>> and writing with >&4 avoids reopening the file on
every iteration, which matters in long loops.
A failed exec ends a script
exec no-such-program
echo "never printed"
/bin/bash: line 1: exec: no-such-program: not found
In a non-interactive shell, an exec whose program cannot be found ends the shell with
status 127. shopt -s execfail makes it return an error instead.
Set the program's name with -a
exec -a my-worker bash -c 'echo "\$0 is $0"; ps -o pid,args -p $$ | tail -n 1'
$0 is my-worker
745 my-worker -c echo "\$0 is $0"; ps -o pid,args -p $$ | tail -n 1 -a NAME sets the argv[0] the program sees, which is what ps shows. Some programs
change behavior based on it; it is also a way to give a long-running worker a
recognizable name.
Run a program with an empty environment with -c
export SECRET=abc
exec -c /usr/bin/env
(no output)
-c clears the environment before running the program, so env prints nothing at all.
env -i program does the same without replacing the shell.
Common pitfalls
Putting
execin the middle of a script by mistake: everything after it is never run.Running
exec cmdin an interactive terminal: whencmdexits, the terminal window closes, because the shell it belonged to is gone.exec >filewithout saving the original descriptor first (exec 3>&1) leaves no way to write to the terminal again.Forgetting
"$@"(quoted) inexec program "$@": unquoted, arguments with spaces are split.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
execwith a command and with redirections is POSIX;-a,-cand-lare bash options that dash does not have. In POSIX sh, file descriptors above 9 are not guaranteed.
Options worth knowing
A selection, described in our own words. For the full list, see bash(1), SHELL BUILTIN COMMANDS.
| Option | What it does |
|---|---|
-a NAME | Pass NAME to the program as its zeroth argument (argv[0]). |
-c | Run the program with an empty environment. |
-l | Put a dash in front of argv[0], as login does, so shells start as login shells. |
Related
sourceRun the commands in a file inside the current shell, so its variables, functions and directory changes stay in effect.commandRun a command while skipping functions and aliases of the same name, or check whether a command exists with -v.nohupRun a command immune to the hangup signal, so it keeps running after you log out or close the terminal.envPrint the environment, or run a command with a modified environment.teeCopy standard input to standard output and to one or more files at the same time.- RedirectionHow >, >>, 2>, 2>&1, &>, < and exec move a command's input and output between files and file descriptors, and why the order matters.
- Running scriptsThe four ways to start a script (./script, bash script, PATH, source), what each one changes, and the errors you get when one goes wrong.
- Process substitutionUse <(cmd) and >(cmd) to hand a command's output to a program that wants a file name, keep while-read loops in the current shell, and avoid its timing traps.
- Traps and signalsRunning cleanup code with trap on EXIT, handling INT and TERM, the ERR pseudo-signal, and the timing and scoping rules that trip people up.