Desk Clarity

A field guide to everyday digital problems.

Developer tools / Practical guide

A Bash Script Exits Zero After Failed Checks: Preserve and Aggregate Status

Save each operation status before logging changes it.

By ON Lab3 min readSources checked November 10, 2026 (KST)

Save each operation status before logging changes it. For a batch that continues after failures, retain a separate nonzero summary and return it explicitly at the end.

Reproduce the behavior safely

Run this self-contained fixture in an empty temporary directory, separate from your project. Shell examples create only named fixture files there. JavaScript examples use local values and send no network requests.

set +e
bash -c 'exit 7'
operation_status=$?
printf 'a later log command succeeded\n'
log_status=$?
printf 'operation=%s log=%s\n' "$operation_status" "$log_status"
test "$operation_status" -eq 7
test "$log_status" -eq 0
# A wrapper that owns the result can later use:
# exit "$operation_status"
summary=0
for chosen_status in 0 7 0; do
  bash -c 'exit "$1"' probe "$chosen_status"
  check_status=$?
  printf 'check status=%s\n' "$check_status"
  if [ "$check_status" -ne 0 ] && [ "$summary" -eq 0 ]; then
    summary=$check_status
  fi
done
test "$summary" -eq 7
printf 'batch status=%s\n' "$summary"
# In a batch wrapper: exit "$summary"

Observed local output

a later log command succeeded
operation=7 log=0
check status=0
check status=7
check status=0
batch status=7

Why the result changes

The log command succeeds while the underlying operation failed. Reporting only the new $? would confuse those two outcomes. The assignment captures the number at its expansion point, before another command can replace it. A wrapper can then finish required cleanup and return the saved operation status deliberately.

Choose and verify the correction

Choose which operation the wrapper promises to report. Capture its status immediately, label cleanup or logging failures separately, and exit with the intended saved status. When using if command; then ... else ... fi, capture the failed command status as the first command in the else branch. Avoid inserting a debug print ahead of that capture. Keep the observed command and its reported status together in a small fixture. For a batch that must continue, keep a separate summary variable and decide whether to preserve the first failure or report a generic nonzero result. The loop fixture preserves the first failure even though the last check succeeds. Keep that loop in the owning shell; a pipeline subshell introduces another state boundary.

Limits and next checks

This example deliberately disables errexit in a disposable process so it can inspect a nonzero status. Pipelines, background jobs and command substitutions require their own status rules; $? is not a history of every stage. The related pipefail guide covers pipeline-stage outcomes. Cleanup failure policy remains an application decision, especially when a successful operation leaves an unusable result after cleanup.

Related: Bash Pipeline Hides a Failure: Check Pipefail and Each Stage’s Status.

Isolated fixture executed on macOS on November 10, 2026 (KST), with GNU bash, version 3.2.57(1)-release (arm64-apple-darwin26). The observed output verifies this example, not every application or runtime.

Sources & further reading