Stage 3 · Build
Shell Scripting & Automation
Robust Bash Options
set -euo pipefail, IFS handling, shellcheck, and defensive quoting patterns.
Strict Mode
# set -e (errexit)
# Exit immediately if a command exits with non-zero status
set -e
false # Script exits here
echo "never" # Never executed
# set -u (nounset)
# Treat unset variables as errors
set -u
echo "$UNDEFINED_VAR" # Error: variable not set
# set -o pipefail
# Return exit code of last failed command in pipeline
set -o pipefail
false | true # Exit code is 1, not 0
# Combined (use in every script)
set -euo pipefail
# Disable for specific commands
command_that_may_fail || true
set +e
risky_command
set -eset -euo pipefail catches most bugs at runtime. Use || true for commands that are expected to fail.
IFS Handling
# Default IFS: space, tab, newline
echo "hello world" | read -r first second
echo "$first" # hello
echo "$second" # world
# IFS splitting behavior
data="one:two:three"
IFS=: read -r a b c <<< "$data"
echo "$a" # one
echo "$b" # two
echo "$c" # three
# Save and restore IFS
OLD_IFS=$IFS
IFS=','
for item in "apple,banana,cherry"; do
echo "$item"
done
IFS=$OLD_IFS
# Use IFS= to preserve whitespace
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# Split string into array
IFS=' ' read -ra items <<< "one two three"
echo "${items[0]}" # oneIFS controls word splitting. Save and restore it when modifying. Use IFS= to read lines without trimming whitespace.
Defensive Quoting
# Always quote variables
name="hello world"
echo $name # WRONG: splits into two arguments
echo "$name" # CORRECT: one argument
# Quote command substitution
files=$(ls)
echo "$files" # Preserves whitespace
# Quote array expansions
arr=("one two" "three four")
echo "${arr[0]}" # "one two"
echo ${arr[0]} # one two (wrong)
# Quote in conditionals
if [[ "$var" == "value" ]]; then
echo "matched"
fi
# Avoid quoting in [[ ]] for pattern matching
if [[ $var == hello* ]]; then
echo "starts with hello"
fi
# Quote in for loops (preserves whitespace)
for file in "/path with spaces"/*; do
echo "$file"
doneAlways quote variables unless you specifically need word splitting. [[ ]] is safer than [ ] because it does not need quoting inside.
shellcheck Analysis
# Install shellcheck
sudo apt install shellcheck
sudo dnf install ShellCheck
# Check a script
shellcheck myscript.sh
# Check with severity levels
shellcheck -S warning myscript.sh # warnings and errors
shellcheck -S info myscript.sh # info, warnings, and errors
# Fix automatically
shellcheck -f diff myscript.sh | patch -p0
# Check inline (for editors)
# Add shellcheck directives
# shellcheck disable=SC2086
echo $unquoted_var
# Common issues shellcheck catches:
# SC2086: Double quote to prevent globbing and word splitting
# SC2046: Quote to prevent word splitting
# SC2006: Use $(..) instead of legacy backticks
# SC2034: Variable appears unused
# SC2155: Declare and assign separatelyshellcheck catches common shell scripting errors before they cause runtime issues. Use it in CI/CD pipelines and editors.
Error Handling Patterns
# Trap-based cleanup
cleanup() {
local exit_code=$?
rm -f "$TEMP_FILE"
exit $exit_code
}
trap cleanup EXIT
# Error handler with line numbers
error_handler() {
echo "Error on line $1, exit code $2" >&2
exit $2
}
trap 'error_handler $LINENO $?' ERR
# Retry with backoff
retry() {
local max_attempts=$1; shift
local attempt=1
while (( attempt <= max_attempts )); do
if "$@"; then
return 0
fi
echo "Attempt $attempt failed, retrying in $((attempt * 2))s..."
sleep $((attempt * 2))
((attempt++))
done
return 1
}
retry 3 curl -s http://example.com
# Assert function
assert() {
if ! "$@"; then
echo "Assertion failed: $*" >&2
exit 1
fi
}
assert test -f "/etc/passwd"
assert grep -q "root" /etc/passwdRetry with backoff handles transient failures. Assert validates preconditions. Trap-based cleanup ensures resources are released.
Debugging Techniques
# Trace execution (show each command before running)
set -x
# + echo hello
# hello
# Disable tracing
set +x
# Debug specific function
debug_func() {
set -x
# function body
set +x
}
# Check variable values
echo "DEBUG: var=$var" >&2
# Print function arguments
debug_args() {
echo "DEBUG: $@=($*)" >&2
echo "DEBUG: $#=($#)" >&2
}
# Verbose flag in scripts
VERBOSE=false
log() { $VERBOSE && echo "[DEBUG] $*" >&2; }
# Debug with bash -x
bash -x myscript.sh
# Debug only a portion
bash -x myscript.sh 2>&1 | grep "+"set -x shows each command before execution. Use it to trace script behavior. Redirect debug output to stderr to keep stdout clean.
shellcheck in CI catches bugs before deployment. Add it as a pre-commit hook or CI step for all shell scripts.
set -e does not catch: commands in conditionals (if, while), commands followed by || or &&, commands in subshells. Be aware of these gaps.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.