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:
exitterminates the current shell context- In sourced scripts (executed with
source/.), there’s no subshell isolation - When
exitruns here, it terminates the calling shell – not a child process true || exit→truesucceeds → Right-side (exit) is skipped ✅ … except when sourced
Why true || exit still triggers termination:
- In sourced scripts,
exitinterprets the entire||chain as part of the top-level environment - Bash evaluates
exitas inheriting the caller’s scope due to POSIX job control rules - Result:
exitbreaks 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) whereexitis 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
- 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 $?
}
- 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:
- Testing gaps: Novices test scripts only via
./script.sh, missing sourced edge cases - Assumption blindness: Assuming
||short-circuiting applies equally toexiteverywhere - Diameter bias: Overfocusing on command logic (
truevsfalse) instead of execution context - Knowledge gaps: Unfamiliarity with Bash’s context layers (subshells/functions/sourced scripts)
Golden Rule: In sourced contexts,
exitdoesn’t exit gracefully – it detonates. Usereturn, 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.