Bash set -e and subshells: error propagation during system upgrades
0 reputation · 14 Jun 2021, 04:32 UTC
0 reputation · 14 Jun 2021, 04:32 UTC
Develop an upgrade script that aborts immediately when any command fails, preventing a half‑upgraded system.
The script must rely on Bash’s documented error‑handling options: set -e (errexit), set -o pipefail for pipelines, and set -o errtrace to propagate traps into subshells. It should also use trap on EXIT for cleanup.
While POSIX defines set -e for simple commands, its effect on commands executed inside subshells invoked by functions or command substitutions is unspecified. Additionally, the interaction between trap handlers and subshell failures is unclear: does an EXIT trap run if a subshell exits with a non‑zero status before the parent script terminates?
set -e automatically propagate to subshells spawned by functions or command substitutions in Bash 5.1?set options ensures consistent error propagation and cleanup across Bash 4.x and 5.x?34775 reputation · 14 Jun 2021, 12:15 UTC
1. Does set -e automatically propagate to subshells spawned by functions or command substitutions in Bash 5.1?
No. In Bash 5.1 (and earlier 4.x), set -e only applies to the current shell. Subshells created by ( … ), command substitutions $( … ), or functions that execute in a child process do not inherit the errexit behaviour unless the inherit_errexit option is enabled.
2. When a subshell fails, is the parent’s EXIT trap guaranteed to execute before the script exits?
Yes, provided the trap is set in the parent and the parent has not already exited or cleared the trap. A subshell that exits with a non‑zero status will cause the parent to run its EXIT trap before the script terminates.
3. What minimal combination of set options ensures consistent error propagation and cleanup across Bash 4.x and 5.x?
set -e – enable errexit for the current shell.set -o pipefail – make pipelines fail if any component fails.set -o errtrace – propagate trap commands into subshells.set -o inherit_errexit so that set -e propagates into subshells automatically.Without inherit_errexit, a subshell that fails silently (e.g., (false)) will not cause the parent to exit. The parent continues, potentially leaving the system in a half‑upgraded state. Adding inherit_errexit makes the subshell failure behave like a normal command failure in the parent, so set -e triggers an immediate exit and the EXIT trap runs.
set -e -o pipefail -o errtrace, defining trap 'echo EXIT trap' EXIT, and calling a function that runs (false) will print the trap message and exit with status 1 on Bash 5.1 when inherit_errexit is enabled.inherit_errexit, the same script continues after the subshell failure, and the EXIT trap still runs only when the script reaches its natural end or when a top‑level command fails.inherit_errexit is unavailable; explicit error checks or wrapping the subshell in a subshell that has set -e are required to guarantee abort.#!/usr/bin/env bashset -e -o pipefail -o errtraceset -o inherit_errexit # only if Bash ≥4.4trap 'echo "Cleaning up…"; perform_cleanup' EXITset -e will cause the parent to exit.(apt-get update)), the failure will propagate because of inherit_errexit.Do you need the script to run on Bash 3.2 (e.g., on very old Debian releases) or only on Bash 4.4+? If older versions must be supported, we’ll need to add explicit error checks or wrap subshells in a set -e subshell.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 14 Jun 2021, 11:58 UTC
To ensure consistent behavior across Bash 4.x and 5.x, it is important to distinguish between the inheritance of set -e and the inheritance of traps. While set -e is inherited by subshells, the ERR trap is not unless set -o errtrace (or set -E) is enabled.
EXIT trap defined in the parent shell will execute if the parent exits due to a failing subshell. However, the parent's EXIT trap does not run inside the subshell; if the subshell requires its own cleanup, it must define its own trap.VAR=$(command_that_fails)) may not trigger errexit if the assignment itself is considered successful.For verification, you can test the parent's reaction to subshell failure using:
bash -c 'set -Ee; trap "echo Parent EXIT" EXIT; (false); echo "This should not print"'