G
GuideDevOps
Lesson 12 of 15

Debugging Bash Scripts

Part of the Shell Scripting (Bash) tutorial series.

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 DevOps

set -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.sh

Example 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