skip to content

In a bash script, what is the difference between the line `exec >>/var/log/job.log 2>&1` and the line `exec /usr/bin/myjob`?

level: middleimportance: should knowfreq 48%

answer

  1. look for the command word
  2. one process before, one process after
  3. the rest of the file may be dead code
  4. redirection scope widens to the whole shell
  5. EXIT traps do not survive it

basics

~20 s

With a command, exec replaces the running shell with that program: same process, same PID, and nothing after the line ever runs. With only redirections and no command, exec applies those redirections to the current shell permanently, and the script continues normally.

solid answer

~40 s

They are the same builtin doing two different jobs. `exec /usr/bin/myjob` replaces the shell's process image with `myjob`: bash does not fork a child, so the new program keeps the shell's PID, its open descriptors and its environment, and the lines after that `exec` — including any `EXIT` trap — never execute, because the shell that would have run them no longer exists. `exec >>/var/log/job.log 2>&1` has no command word, so there is nothing to replace the shell with; instead the redirections are applied to the *current* shell and stay in effect for the rest of the script, which is the usual way to send everything a script prints from that point onward into a log file. The rule is simply: command present means replace the process, command absent means rewire this shell.

code

bash · 7 lines
bash
#!/usr/bin/env bash
exec >>/tmp/job.log 2>&1     # no command: rewires THIS shell from here on
echo "starting"              # written to /tmp/job.log

trap 'echo "cleanup ran"' EXIT
exec sleep 1                 # command: this shell is replaced by sleep
echo "never printed"         # unreachable, and the EXIT trap never fires

go deeper

for a junior

Recognise both spellings on sight: exec followed by a program replaces the shell, exec followed only by redirections changes where the script's output goes. Know that nothing after the replacing form runs.

for a middle

Explain that no fork happens, so the PID and open descriptors are kept while shell-only state is discarded, and that a redirection through exec widens its scope from one command to the whole shell.

for a senior

Show the review instinct: code after an exec line is dead, an EXIT trap before it never fires, and a failed exec ends a non-interactive script unless shopt -s execfail is set. Explain when a wrapper should hand off versus stay alive.

for a principal

Frame it as a process-model choice for services you operate. Handing off keeps one process and one identity for supervision; keeping the shell buys you cleanup and fallback logic at the cost of a supervisor layer you now have to make signal-correct.

## One builtin, two behaviours `exec` is a bash builtin whose behaviour hinges on whether a command word follows it. **With a command** — `exec /usr/bin/myjob` — bash does not create a new process. Normally, running an external command means bash makes a child and the child becomes that program; `exec` skips the child and turns *this* process into the program directly. The consequences follow from that one fact: - The PID is unchanged. Whatever was watching the shell — a supervisor, a container runtime, a parent script's `wait` — is now watching `myjob` under the same number. - Open file descriptors, the current directory and the exported environment carry over, because they are properties of the process and the process survived. - Everything that was purely *shell* state is gone: functions, non-exported variables, aliases and traps existed only inside bash's memory, and that memory has been replaced. - Nothing after the `exec` line runs. Not the next command, not an `EXIT` trap, not a cleanup function. The script does not "return" from `exec`. ```bash trap 'echo cleanup' EXIT exec /bin/true # the trap never fires; bash is gone echo "unreachable" ``` **Without a command** — `exec >>/var/log/job.log 2>&1` — there is nothing to replace the shell with, so bash keeps running and just performs the redirections on itself. Ordinarily a redirection lasts only for the command it is attached to; applied through `exec`, it changes the shell's own descriptors, so every later command in the script inherits the new destination. This is the standard opening move for a script that must log unattended: ```bash #!/usr/bin/env bash exec >>/var/log/job.log 2>&1 echo "starting run at $(date)" # lands in the log, as does everything after it ``` The detailed mechanics of duplicating and saving descriptors — numbering, `n>&m`, restoring what you replaced — are a topic of their own; what matters here is the *scope* change: from one command to the whole shell. ## Why the replacing form exists Three recurring reasons: 1. **Handing off cleanly.** A wrapper script sets up environment, resolves configuration, then `exec`s the real program. Without `exec` you keep an idle bash sitting in the process tree for the entire lifetime of the program, doing nothing but waiting, and standing between the program and whoever signals it. 2. **Keeping the identity.** Supervisors, container runtimes and anything tracking a PID keep tracking the right process, because there is only ever one process. 3. **Saving a process.** Marginal on a desktop, real when a script fans out thousands of short-lived invocations. ## Failure and options If the command cannot be executed, a **non-interactive** shell exits — `exec` is not a normal command whose failure you can shrug off. `shopt -s execfail` changes that: a non-interactive shell will not exit when `exec` fails, so the script can fall back to another path. Without it, an `exec` of a mistyped binary ends the script on the spot. `exec` also takes a few options worth knowing: `-a name` runs the program with a different `argv[0]` (which is what shows up in a process listing), `-c` runs it with an empty environment, and `-l` puts a dash in front of `argv[0]` the way a login shell is started. ## The trap that surprises people Because `exec cmd` destroys the shell, any cleanup you registered with `trap ... EXIT` is silently abandoned. A script that creates a temporary directory, registers a trap to remove it, and then `exec`s the real program will leave that directory behind forever. Either do the cleanup before the `exec`, or do not `exec` at all and let the shell stay alive to run its trap — accepting the extra process and taking responsibility for forwarding signals to the child. ## Reading it in review When you see `exec` in a script, first check for a command word. No command word means "the rest of this script's output goes somewhere else", and you keep reading. A command word means "this script ends here", and everything below it is dead code — which, in a review, is worth flagging as a bug in itself.

  • Your script creates a temp directory with a trap EXIT cleanup and then execs the real binary. What happens to the temp directory?
    It is never removed. `exec` replaces the shell, and the trap lived in shell memory that no longer exists, so nothing runs it. Either clean up explicitly before the `exec`, or keep the shell alive to run the child and accept that you now own forwarding signals to it.
  • What happens if the program named in an exec line does not exist?
    A non-interactive shell prints an error and exits immediately — the script stops there rather than continuing to the next line. `shopt -s execfail` makes bash keep going after a failed `exec` instead, which lets a script try a fallback path. Interactive shells never exit on a failed exec.
  • Does exec cmd inherit the shell's functions and local variables?
    No. Only process-level state survives: PID, open file descriptors, current directory, resource limits and the exported environment. Shell functions, aliases, traps and non-exported variables lived inside bash's own memory and are destroyed along with it. If the new program needs a value, it must be exported first.

saying these in an interview costs you the question

  • Thinks exec always starts a new process
  • Believes code after exec cmd still runs
  • Assumes an EXIT trap fires after exec replaces the shell
  • Cannot tell the redirection-only form from the replacing form
  • Thinks shell functions carry over into the exec'd program

context