Skip to content

Debugging

Debugging bash scripts is essential for DevOps engineers. This chapter covers various techniques to identify and fix issues in bash scripts, from simple echo statements to advanced debugging tools.


#!/bin/bash
# Enable command tracing
set -x # Start tracing
echo "Hello World"
ls -la
set +x # Stop tracing
echo "Tracing disabled"
#!/bin/bash
# Print shell input lines as they are read
set -v
echo "This will be printed"
cat <<EOF
This here document
will be printed
line by line
EOF
set +v
#!/bin/bash
# Multiple options
set -xv
# Or using flags
set -o xtrace -o verbose

OptionFlagDescription
errexit-eExit on error
nounset-uExit on undefined variable
pipefail-o pipefailPipeline fails on error
xtrace-xPrint commands before execution
verbose-vPrint input as read
noexec-nCheck syntax without executing
#!/bin/bash
# Recommended for development
set -euo pipefail -x
# In production (less verbose)
set -euo pipefail

#!/bin/bash
# Simple debug output
debug() {
echo "[DEBUG] $*"
}
debug "Starting script"
debug "Variable value: $myvar"
# Conditional debug
DEBUG=1
if [[ $DEBUG -eq 1 ]]; then
set -x
fi
#!/bin/bash
# Customize trace output
# Default: + script.sh:linenumber:command
# Custom: show more info
export PS4='+[$(date +%H:%M:%S)] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
echo "Hello"
myvar="test"
echo "$myvar"

Terminal window
# Debug script execution
bash -x script.sh
# Debug with specific options
bash -x -e script.sh
bash -x -u script.sh
bash -x -o pipefail script.sh
# Debug specific functions
bash -c 'source script.sh; function_name'
Terminal window
# Syntax check only
bash -n script.sh
# Interactive debugging
bash -i script.sh
# Trace specific portions
{ set -x; command; set +x; }

Terminal window
# Install shellcheck (Arch Linux)
sudo pacman -S shellcheck
# Run shellcheck
shellcheck script.sh
# With specific options
shellcheck -s bash script.sh
shellcheck -e SC1090,SC1091 script.sh # Ignore specific warnings
# Enable all available checks
shellcheck -a script.sh
Terminal window
# SC2086: Quote variables
# shellcheck disable=SC2086
echo $var # Quoted version: echo "$var"
# SC1090: Can't follow non-constant source
# shellcheck disable=SC1090
source "$SCRIPT"
# SC2029: Use escape for server-side variables
# shellcheck disable=SC2029
ssh user@host "echo $local_var"

Terminal window
# Install
sudo pacman -S bashdb
# Run debugger
bashdb script.sh
# Debugger commands:
# break [file:]line
# continue
# step
# next
# print $variable
# watch variable
# backtrace

#!/bin/bash
# Problem: using unset variable
set -u
echo "$undefined_var" # Error!
# Fix: use default value
echo "${undefined_var:-default}"
# Or set default
: "${VAR:=default}"
#!/bin/bash
set -e
# Problem: command failure stops script
false
echo "This never runs"
# Fix: handle failure
false || echo "Command failed"
false && echo "Command succeeded"
# Or use command that always succeeds
false ; true
#!/bin/bash
# Problem: word splitting
arr=( $(ls) ) # Breaks on spaces
# Fix: use globbing
arr=( * ) # Safe
# Or use read with IFS
read -ra arr <<< "$(ls)" # Still problematic
mapfile -t arr < <(ls) # Best

#!/bin/bash
# Logging function
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a /var/log/script.log
}
log INFO "Starting script"
log ERROR "Something went wrong"
log DEBUG "Variable value: $var"
#!/bin/bash
DEBUG="${DEBUG:-0}"
debug() {
if [[ "$DEBUG" == "1" ]]; then
echo "[DEBUG] $*" >&2
fi
}
debug "Processing file: $filename"

Terminal window
# Print all variables
declare -p
# Print environment variables
printenv
# Print functions
declare -F
declare -f function_name
#!/bin/bash
# Run before each command
DEBUG_TRAP() {
echo "About to run: $BASH_COMMAND"
}
trap DEBUG_TRAP DEBUG
echo "Hello"
ls

#!/bin/bash
# Capture trace to file
exec 4>&2 # Save stderr to fd 4
exec 2> >(tee /tmp/trace.log)
set -x
echo "Hello"
ls -la
#!/bin/bash
# Debug mode
DEBUG="${DEBUG:-0}"
if [[ "$DEBUG" == "1" ]]; then
set -x
fi
# Or toggle
debug_on() { set -x; }
debug_off() { set +x; }

#!/usr/bin/env bash
# =============================================================================
# Script Template with Debugging
# =============================================================================
set -o errexit # Exit on error
set -o nounset # Exit on unset variable
set -o pipefail # Exit on pipeline error
# Debug mode
DEBUG="${DEBUG:-0}"
if [[ "$DEBUG" == "1" ]]; then
set -o xtrace
fi
# Logging
LOG_FILE="${LOG_FILE:-/tmp/script.log}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
log_error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2
}
# Cleanup trap
cleanup() {
local exit_status=$?
log "Cleaning up (exit status: $exit_status)"
# Add cleanup code here
exit $exit_status
}
trap cleanup EXIT
# ============================================================================
# Main Script
# ============================================================================
main() {
log "Starting script"
# Your code here
echo "Hello, World!"
log "Script completed"
}
main "$@"

In this chapter, you learned:

  • ✅ Using set -x for tracing
  • ✅ Using set -v for verbose output
  • ✅ Debug mode flags and options
  • ✅ PS4 for better tracing
  • ✅ Using shellcheck for linting
  • ✅ Common issues and fixes
  • ✅ Logging strategies
  • ✅ Variable inspection
  • ✅ Advanced debugging techniques
  • ✅ Real-world debugging template

Continue to the next chapter to learn about Strict Mode and best practices.


Previous Chapter: Exit Status and Error Handling Next Chapter: Strict Mode (set -euo pipefail)