Debugging Bash scripts is essential when things go wrong. Instead of guessing, use these built-in tools and techniques to identify the root cause of issues.
1. Using set flags
Bash provides set options that are incredibly useful for debugging. You can add these at the top of your script after the shebang.
set -x (Enable Trace)
This prints each command before it's executed, showing variable expansions.
Script:
#!/bin/bash
set -x
NAME="DevOps"
echo "Hello $NAME"Output:
+ NAME=DevOps
+ echo 'Hello DevOps'
Hello DevOpsset -e (Exit on Error)
The script will exit immediately if any command fails (returns a non-zero exit code). This prevents a small error from cascading.
2. Debugging Parts of a Script
You don't have to debug the whole script. You can enable/disable debugging for specific blocks.
#!/bin/bash
echo "This part is normal"
set -x # START DEBUGGING
# Complex logic here
set +x # STOP DEBUGGING
echo "This part is normal again"3. Common Error Scenarios
Unbound Variables
By default, Bash treats unset variables as empty strings. This can lead to silent failures (e.g., rm -rf $DIR/* if $DIR is empty!).
Solution: set -u
This will cause the script to exit if an undefined variable is accessed.
Pipeline Failures
By default, set -e only checks the last command in a pipeline.
echo "test" | grep "fail" | cat will succeed even if grep fails.
Solution: set -o pipefail
This ensures the return code of the entire pipeline is the code of the last command that failed.
4. ShellCheck: The Linter for Bash
ShellCheck is an open-source tool that finds bugs and potential issues in your scripts. It's highly recommended for every DevOps engineer.
Command:
shellcheck script.shExample Output:
In script.sh line 5:
if [ $NAME = "Alice" ]; then
^-- SC2086: Double quote to prevent globbing and word splitting.5. Using trap for Debugging
The trap command can catch signals or the DEBUG signal to execute code before every command.
#!/bin/bash
# Print line number before each command
trap 'echo "Executing line $LINENO"' DEBUG
ls
echo "Done"Summary: The "Safety Header"
For production DevOps scripts, consider using this header to make them robust and easy to debug:
#!/bin/bash
set -euo pipefail
# -e: exit on error
# -u: exit on unset variable
# -o pipefail: catch errors in pipelines