Chapter 8: Error Handling and Debugging¶
Learning Objectives¶
By the end of this chapter, you will be able to:
- Understand how Bash uses exit codes to communicate success and failure.
- Use the
trapcommand to catch and handle errors and signals in your scripts. - Debug Bash scripts using
set -xandbash -x. - Implement logging and output redirection for troubleshooting.
- Apply best practices for debugging on macOS.
Introduction: Why Error Handling and Debugging Matter¶
Even the best Bash scripts fail at some point. On macOS, where scripts often automate system configurations, backups, or security checks, robust error handling is crucial. Poor error handling can lead to partial changes, corrupted files, or security misconfigurations. This chapter shows you how to detect and handle problems gracefully, log what happened, and troubleshoot effectively.
8.1 Understanding Exit Codes¶
Every command in Bash returns an exit code. By convention:
0means success.- Any non-zero value indicates an error.
You can access the last command’s exit code with $?.
#!/bin/bash
echo "This will succeed"
ls /tmp
echo "This will fail"
ls /nonexistent
echo "Exit code of last command: $?"
Using Exit Codes in Scripts¶
You should check exit codes to make decisions.
#!/bin/bash
ping -c 1 8.8.8.8 >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Network unreachable. Exiting."
exit 1
else
echo "Network is reachable."
fi
8.2 Using trap to Handle Errors and Signals¶
trap allows you to run cleanup code when a script exits, or when it receives a signal like SIGINT (Ctrl+C).
Example: Clean up a temp file on exit¶
#!/bin/bash
TEMP_FILE="/tmp/my_tempfile.txt"
trap "echo 'Cleaning up'; rm -f \"$TEMP_FILE\"; exit" EXIT
echo "Doing work..."
touch "$TEMP_FILE"
sleep 5
echo "Done."
When the script exits — normally or via Ctrl+C — trap ensures the temp file is removed.
Common trap Use Cases¶
- Remove sensitive temporary files.
- Stop services you started.
- Unlock files or resources.
8.3 Debugging with set -x and bash -x¶
Bash’s -x option shows each command and its arguments as they run.
Temporary Debugging¶
Insert set -x to start debugging, and set +x to stop:
Debugging the Entire Script¶
Run the script with bash -x:
On macOS, this is helpful when working with launchd jobs — you can test scripts interactively before deploying them.
8.4 Logging and Output Redirection¶
Capturing output and errors helps you diagnose issues after the fact.
Redirect stdout and stderr to a log file¶
#!/bin/bash
LOGFILE="/tmp/myscript.log"
echo "Starting script" > "$LOGFILE" 2>&1
{
echo "This is stdout"
ls /nonexistent # This will cause an error
} >> "$LOGFILE" 2>&1
echo "Script done" >> "$LOGFILE"
Best Practices¶
- Always redirect both stdout and stderr for full context.
- Rotate logs on macOS if you run scripts regularly (use
newsyslogor log rotation tools). - Include timestamps for each log entry with
date.
8.5 macOS-Specific Tips¶
- When testing scripts that will run as
launchdjobs, log output to/var/log/or~/Library/Logs/. - For privileged scripts, remember
sudocan suppress environment variables; test exit codes carefully. - Use
/usr/bin/loggerto write messages to the macOS unified log:
You can view these with log show or the Console app.
Chapter 8 Exercise¶
Add robust error handling and debugging to an existing script¶
- Take a script you wrote in a previous chapter.
- Add:
- Exit code checks after each critical command.
- A
trapto clean up onEXIT. set -xor run withbash -xto debug.- Redirect output to a log file with both stdout and stderr.
- (Optional) Log key events to the macOS system log using
logger.
Example snippet:
#!/bin/bash
trap "echo 'Caught EXIT'; exit" EXIT
set -x
echo "Doing work"
mkdir /restricted # Should fail without sudo
echo "Exit code: $?"
set +x
macOS Scripting Tips¶
- Use clear, descriptive log messages.
- Always test your
traphandlers to avoid infinite loops. - Clean up sensitive files or secrets when an error occurs.
- For persistent issues, break the script into small parts and test each with
bash -x.