Bash command ‘true || exit’ exits the program

Bash Pitfall: Why true || exit Exits Your Shell (And How To Avoid It)

Summary

Encountering true || exit causing an immediate shell termination? This unexpected behavior occurs when the command runs in a sourced script context. Despite true succeeding (which should skip the right side of ||), Bash still executes exit and terminates your current terminal session. The core issue is context-dependent shell behavior, where exit escapes all enclosing environments in sourced scripts – including your active shell.

Root Cause

The behavior stems from how Bash handles job control contexts and the exit command’s scope:

  • exit terminates the current shell context
  • In sourced scripts (executed with source/.), there’s no subshell isolation
  • When exit runs here, it terminates the calling shell – not a child process
  • true || exittrue succeeds → Right-side (exit) is skipped ✅ … except when sourced

Why true || exit still triggers termination:

  1. In sourced scripts, exit interprets the entire || chain as part of the top-level environment
  2. Bash evaluates exit as inheriting the caller’s scope due to POSIX job control rules
  3. Result: exit breaks out of all contexts, killing your terminal session

Why This Happens in Real Systems

This issue surfaces most often in:

  • Environment setup scripts (e.g., . ~/.envrc) where exit is used for error handling
  • Library scripts sourced into larger Bash programs
  • Commands aliased with safety checks (e.g., alias safe_cd='cd $1 || exit')
  • Infrastructure tooling like Terraform/AWS CLI wrappers sourced via .

Real-World Impact

Negative consequences include:
💥 Terminal/Tab crashes: Lost work and context from terminated sessions
⚠️ CI/CD pipeline failures: Sourced scripts unexpectedly aborting pipelines
🔒 Security misconfigurations: Hardened scripts escaping sandboxed contexts
📉 Productivity drains: Debugging time wasted on misattributed crashes

Example or Code

❌ Problematic patterns

Sourced script (danger.sh):

# THIS TERMINATES SHELL IF SOURCED
setup() { 
  cd "$1" || exit 1  
}

Test execution:

# Create valid directory
mkdir example && cd example

# Source the script - SHELL EXITS HERE!
source danger.sh

# This line never runs
echo "Shell terminated unexpectedly"

✅ Fixed Approach

Replace exit with return for sourced scripts:

# Safe version: Uses return for sourced context
safe_setup() {
  cd "$1" || return 1
}

Execution outcome:

source safe_script.sh
safe_setup valid_dir  # Fails gracefully if cd fails
echo "Still running!" # Script continues

Key distinctions:
| Command | Subshell Scripts (./script.sh) | Sourced Scripts (source script.sh) |
|——————|———————————-|————————————–|
| exit | Ends subshell | Terminates parent shell 💥 |
| return | Syntax error | Exits function/sourced script ✅ |

How Senior Engineers Fix It

Prevention Strategy

  1. Context-aware termination
    
    # Universal error exit: Works in both contexts
    exit_or_return() {
    # Use 'return' if script is sourced
    [[ "${BASH_SOURCE[0]}" == "$0" ]] && exit "$1" || return "$1"
    }

Usage:

cd ~/projects || exit_or_return 127


2. **Explicit sourcing guards**  
```bash
# Only run main logic in non-sourced mode
[[ "${BASH_SOURCE[0]}" == "$0" ]] && {
  main "$@"
  exit $?
}
  1. Defensive aliasing:
    # Avoid exit-bound aliases/functions
    safe_cd() { cd "$@" || return; }
    alias cd='safe_cd' # Override cd behavior

Pro Tips:

  • Validation: Detect sourcing with [[ ${#FUNCNAME[@]} -gt 1 ]]
  • Logging: Always echo >&2 "Error: msg" before exiting
  • Reproducibility: Test scripts both ways (source/./)

Why Juniors Miss It

This pitfall escapes detection because:

  1. Testing gaps: Novices test scripts only via ./script.sh, missing sourced edge cases
  2. Assumption blindness: Assuming || short-circuiting applies equally to exit everywhere
  3. Diameter bias: Overfocusing on command logic (true vs false) instead of execution context
  4. Knowledge gaps: Unfamiliarity with Bash’s context layers (subshells/functions/sourced scripts)

Golden Rule: In sourced contexts, exit doesn’t exit gracefully – it detonates. Use return, check $BASH_SOURCE, or structure important scripts to run as standalone executables. When designing shared scripts, always assume they’ll be sourced – and defend accordingly.

Leave a Comment