Bash troubleshooting
Set Bash "Strict Mode"
Use set -euo pipefail at the top of the script to exit the script if any command fails and treat references to unset/undefined variables as an error.
Script general check
Use ShellCheck to find bugs and errors in the script.
Script exits early or silently fails
Run bash -x script.sh.
Verify shebang and chmod +x.
Command not found / wrong PATH
echo "$PATH", which , type -a . Cron and systemd use minimal PATH — use absolute paths.
Permission denied on redirect or script
Redirects run before the command — target file permissions matter.
Zombie or stuck background process
Use ps aux or pstreeto identify the parent process. Zombies cannot be killed directly; the parent must reap them or be restarted.
Debugging Output
Print variable state at failure points
echo "DEBUG: var=$var, status=$?" >&2Send debug output to stderr so it doesn't pollute stdout or pipes.
Trace a specific section (not the whole script)
set -x
# code you want to trace
set +xBetter than running bash -x on the whole script when only one section is suspect.
Quoting & Whitespace
Unquoted variables break on spaces/globs
# Bad — breaks if $file has spaces
cp $file /dest/
# Good
cp "$file" /dest/The majority of subtle bash bugs come down to missing quotes. When in doubt, quote it.
Exit Codes
Check $? immediately — it gets overwritten
some_command
status=$? # capture right away
if [[ $status -ne 0 ]]; then ...Also remember $? reflects the last command, so a echo or [[ ]] check can clobber it.
Pipelines hide failures
# Only the last command's exit code is returned by default
cat file | grep "x" | sort
# Use pipefail or check PIPESTATUS
echo "${PIPESTATUS[@]}"Subshells & Scope
Variables set in subshells don't propagate up
# This won't work — the while loop runs in a subshell
cat file | while read line; do
result="$line" # lost after pipe ends
done
# Use process substitution instead
while read -r line; do
result="$line"
done < cat fileThis is a classic gotcha when piping into while read.
Conditionals & Test
[ vs [[ — use [[ in bash
[ "$var" == "x" ] # works in POSIX shells.
[[ "$var" == "x" ]] # is Bash-specific and avoids word splitting and pathname expansion.Check if a variable is empty vs unset
[[ -z "$var" ]] # true if empty OR unset
[[ -v var ]] # true only if set (even if empty)
[[ -n "$var" ]] # true if non-emptyFile & Path Issues
Trailing newlines in command substitution
content=$(cat file) # strips trailing newlines silentlyThis is usually fine, but can surprise you with binary data or intentional blank lines.
Relative vs absolute paths in scripts
Scripts don't run from where you think — use this to anchor paths:
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" Anchors paths relative to the script location rather than the current working directory.Functions & Return Values
Functions can't return strings — only exit codes (0–255)
# Wrong expectation
get_name() { return "alice"; }
# Correct pattern — use echo + capture
get_name() { echo "alice"; }
name=$(get_name)IFS & Input Parsing
Unexpected field splitting from IFS
# If IFS contains space/tab/newline, variable expansion splits
IFS=,
read -r a b c <<< "one,two,three" # splits on comma
# Reset when done
IFS=$' \t\n'Check IFS first when read or loops produce weird splits.
Reading lines safely
Without IFS= and -r, leading/trailing whitespace and backslashes may be modified unexpectedly.
# Correct usage
while IFS= read -r line; do
...
done < fileThis is a common gotcha when reading lines from a file.
Heredocs
Indentation breaks heredocs unless you use <<-
# Fails — leading tabs/spaces are literal
cat << EOF
hello
EOF
# Use <<- to strip leading tabs (not spaces)
cat <<- EOF
hello
EOFLogging Pattern (General Best Practice)
A simple reusable logger saves a lot of echo noise:
log() { echo "[$(date +%T)] $*" >&2; }
die() { log "ERROR: $*"; exit 1; }
log "Starting backup..."
[[ -d "$SRC" ]] || die "Source dir not found: $SRC"Practice scenarios
Hands-on Bash scenarios on live Linux VMs: bash