Bash shell.

Debugging and Troubleshooting Your Bash Scripts

This guide and cheat sheet, explores various techniques and tools for troubleshooting and debugging your Bash scripts. We’ll cover both built-in approaches (like set options and trap) and external resources (shellcheck, logs) to help you quickly identify and fix issues.

Introduction to Bash Debugging

Debugging Bash scripts is essential for maintaining reliable and predictable automation. Even the simplest script can fail if not thoroughly tested and debugged. Understanding common pitfalls—like improperly handled variables, syntax issues, or unexpected exit codes—can save time and frustration.

Effective debugging and troubleshooting is a critical skill for every Bash script author. By combining built-in features like set -x, trap, and PS4 with external tools such as shellcheck, you can quickly isolate and resolve script issues. Implementing robust error handling, thorough logging, and best practices like incremental development will further ensure that your scripts run smoothly and remain maintainable in the long term (which you’ll always be your number one goal: stability and performance!).

The basics

This guide is exclusively about debugging and finding problems in your Bash scripts. For more general guides about Bash scripting check these articles out:


Basic Debugging Options

The set Built-in

Bash provides several options with the set command to manage error handling and debugging:

  • -e: Exit on any command returning a non-zero status.
  • -u: Treat unset variables as errors.
  • -o pipefail: Pipeline returns the exit code of the first failed command.
  • -x: Print each command before executing it (trace mode).
  • -v: Print shell input lines as they’re read (verbose mode).
#!/usr/bin/env bash
set -euxo pipefail
#"e" = exit on error
#"u" = fail if uninitialized variable is used
#"x" = trace mode
#"o pipefail" = exit if any command in a pipeline fails
my_var="Hello"
echo $my_var

When you run the script above, Bash will show each command as it executes and exit immediately if any command fails.

Enabling/Disabling Debugging Inline

You can also toggle debugging at specific parts of your script to minimize console noise:

#!/usr/bin/env bash
echo "Starting script…"
#Enable debugging
set -x
#Debugged section
ls -l /tmp
whoami
#Disable debugging
set +x

echo "Back to normal output."

Using trap for Error Handling

Cleanup and Logging

You can use trap to handle signals (e.g., SIGINT) or errors. This helps you run cleanup or logging commands before exiting.

#!/usr/bin/env bash
cleanup() {
echo "An error occurred. Exiting…"
# Additional cleanup or logging here
exit 1
}
Trap on any command error, SIGINT, or script exit
trap cleanup ERR INT

echo "Executing risky commands…"
#Commadn that might fail
false

echo "This line won't be reached if 'false' fails."

In the example above, the script calls the cleanup function if any command fails (ERR) or if the user presses Ctrl-C (INT).

Capturing the Exit Code

Another way to manage errors is to test each command’s exit status:

command_output=$(some_command)
if [ $? -ne 0 ]; then
echo "some_command failed, got exit code $?"
exit 1
fi

While this approach is more verbose, it provides precise control over what happens after each command.

Logging and Redirection

Redirecting Output to a File

Redirecting script output and errors to a log file can help you analyze failures afterward.

#!/usr/bin/env bash
LOGFILE="/var/log/myscript.log"

{
echo "Script started at $(date)"
# Commands here
echo "Script completed."
} &>> "$LOGFILE"

Using &>> sends both standard output and standard error to the LOGFILE.

3.2 Syslog Integration

On servers, you can use logger to send messages to syslog, enabling centralized logging.

logger "Script started."
#…
logger "Error encountered in script."

Advanced Debugging

PS4 Variable

Customizing the PS4 variable can provide more informative debugging. For instance:

export PS4='+ ${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
set -x

This prints the file name, line number, and function name before each traced command, making it easier to pinpoint the exact script location.

Shellcheck

Shellcheck is a popular static analysis tool that scans your Bash script for potential issues such as syntax errors, deprecated features, and common pitfalls.

shellcheck myscript.sh

Shellcheck suggestions can help you fix problems before even running your script.

Process Substitution for Debugging

Process substitution can let you view the output of multiple commands side by side, which can be helpful when diagnosing issues:

diff <(some_command) <(some_command --debug)

Comparing the normal output to the debug output of the same command can highlight discrepancies.


5. Best Practices for Debugging

  1. Incremental Development: Build scripts in small, testable chunks. Test each section before moving on.
  2. Use Functions: Break code into functions, making it easier to isolate and test functionality.
  3. Error Messages: Provide detailed messages when errors occur so you can quickly identify the root cause.
  4. Validate Inputs: Check if variables are set or files exist before operating on them.
  5. Version Control: Keep your scripts in a version control system (e.g., Git) to track changes and roll back if a bug appears.
  6. Test on Multiple Shell Versions: If portability is crucial, ensure your script works on other shells like dash or older Bash versions.

Leave a Reply

Your email address will not be published. Required fields are marked *