A
cd ..
Tools

Bash Debugging

Debug bash scripts with set options, traps, and shellcheck.

2025-10-29
bash, debugging, scripting

Enable trace mode

set -x  # Print commands as they execute

Debug specific section

set -x
# Code to debug
set +x

Run script in debug mode

bash -x script.sh

Verbose mode

set -v  # Print shell input lines
bash -v script.sh

Exit on error

set -e

Exit on undefined variable

set -u

Pipe fail

set -o pipefail

Combined safety settings

#!/bin/bash
set -euo pipefail

Custom PS4 prompt

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

Trap errors

#!/bin/bash

error_handler() {
    echo "Error occurred in script at line: ${1}" >&2
    exit 1
}

trap 'error_handler ${LINENO}' ERR

Trap exit

cleanup() {
    echo "Cleaning up..."
    rm -f /tmp/tempfile
}

trap cleanup EXIT

Trap signals

trap 'echo "Interrupted"; exit 1' INT TERM

Debug function

debug() {
    if [ "$DEBUG" = "true" ]; then
        echo "DEBUG: $*" >&2
    fi
}

export DEBUG=true
debug "This is a debug message"

Check syntax without running

bash -n script.sh

Shellcheck

# Install
sudo apt install shellcheck

# Check script
shellcheck script.sh

Common shellcheck fixes

# Quote variables
echo "$VAR" # Not: echo $VAR

# Use [[ ]] instead of [ ]
if [[ $VAR = "value" ]]; then

# Check command existence
if command -v docker &>/dev/null; then

# Use $() instead of backticks
result=$(command) # Not: result=`command`

Assert function

assert() {
    if ! "$@"; then
        echo "Assertion failed: $*" >&2
        exit 1
    fi
}

assert [ -f "important-file.txt" ]

Profiling script

#!/bin/bash

TIMEFORMAT='%R seconds'

time {
    # Code to profile
    sleep 2
    echo "Done"
}

Xtrace to file

exec 5> debug.log
BASH_XTRACEFD="5"
set -x

# Your script here

Conditional debugging

#!/bin/bash

if [ "${DEBUG:-0}" = "1" ]; then
    set -x
fi

# Run with: DEBUG=1 ./script.sh

Function call stack

print_stack() {
    local i=0
    local FRAMES=${#BASH_SOURCE[@]}

    echo "Traceback (most recent call last):" >&2
    
    for ((i=FRAMES-1; i>=0; i--)); do
        echo "  File \"${BASH_SOURCE[i]}\", line ${BASH_LINENO[i-1]}, in ${FUNCNAME[i]}" >&2
    done
}

trap print_stack ERR

Variable inspection

inspect_vars() {
    echo "=== Variable Inspection ===" >&2
    echo "PWD: $PWD" >&2
    echo "ARGS: $*" >&2
    env | grep "^MY_" >&2
}

trap inspect_vars DEBUG

Dry run mode

DRY_RUN=${DRY_RUN:-false}

run() {
    if [ "$DRY_RUN" = "true" ]; then
        echo "Would run: $*"
    else
        "$@"
    fi
}

run rm -rf /tmp/test

Log levels

LOG_LEVEL=${LOG_LEVEL:-INFO}

log_debug() { [ "$LOG_LEVEL" = "DEBUG" ] && echo "DEBUG: $*" >&2; }
log_info() { echo "INFO: $*"; }
log_warn() { echo "WARN: $*" >&2; }
log_error() { echo "ERROR: $*" >&2; }

Breakpoint function

breakpoint() {
    echo "=== Breakpoint at line $BASH_LINENO ===" >&2
    echo "Variables:" >&2
    set | grep "^[A-Z]" >&2
    read -p "Press Enter to continue..." >&2
}

# Use in script
breakpoint

Interactive debugger

debug_shell() {
    echo "Entering debug shell (type 'exit' to continue)" >&2
    bash
}

trap 'debug_shell' DEBUG

Checkpoint logging

checkpoint() {
    echo "[$(date +'%T')] CHECKPOINT: $1" >&2
}

checkpoint "Starting processing"
# ... code ...
checkpoint "Processing complete"

Execution timer

#!/bin/bash

START_TIME=$(date +%s)

cleanup() {
    END_TIME=$(date +%s)
    ELAPSED=$((END_TIME - START_TIME))
    echo "Script ran for $ELAPSED seconds"
}

trap cleanup EXIT

Common debugging patterns

#!/bin/bash

# Function arguments
myfunc() {
    echo "Called with $# arguments: $*" >&2
    # ... function code ...
}

# Check if variable is set
: "${REQUIRED_VAR:?Variable REQUIRED_VAR must be set}"

# Default values
VAR=${VAR:-default_value}

# Readonly variables
readonly CONFIG_FILE="/etc/app.conf"

Debugging script template

#!/bin/bash

# Debugging flags
set -euo pipefail
IFS=$'\n\t'

# Debug mode
DEBUG=${DEBUG:-0}
if [ "$DEBUG" = "1" ]; then
    set -x
    export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
fi

# Error handling
error_exit() {
    echo "Error: $1" >&2
    exit 1
}

trap 'error_exit "Script failed at line $LINENO"' ERR

# Cleanup
cleanup() {
    echo "Cleaning up..."
}

trap cleanup EXIT

# Main script
main() {
    echo "Script started"
    # Your code here
}

main "$@"

bashdb debugger

# Install
sudo apt install bashdb

# Run with debugger
bashdb script.sh

# Commands:
# s - step
# n - next
# c - continue
# p $var - print variable
# b 20 - breakpoint at line 20
# l - list code
# q - quit

Was this useful?

Share with your team

Browse More