Skip to content
daniel@MacBookPro:~
Shell & TerminalHow-To September 23, 2026 3 min read

How to Debug Shell Scripts With set -x and ShellCheck

A complete walkthrough of the two genuinely essential shell scripting debugging tools — one that traces exactly what a script actually executes, and one that catches whole categories of bugs before the script ever runs.

Shell scripts fail in ways that are often genuinely confusing to diagnose from error messages alone — these two tools cover the two halves of effective shell script debugging: tracing what actually executed, and catching bugs before execution entirely.

Step 1: enable execution tracing with set -x

#!/bin/bash
set -x
# your script's actual commands

With set -x enabled, Bash prints every command it’s about to execute, with variables and expansions already resolved, immediately before running it — showing you exactly what the script is doing, not what you assumed it would do.

Step 2: enable tracing for just a specific section

set -x
# the specific section you're debugging
set +x

Rather than tracing an entire long script (producing overwhelming output), bracket just the specific section you suspect is misbehaving with set -x and set +x.

Step 3: run a script with tracing from the command line without editing it

bash -x myscript.sh

Useful for one-off debugging without needing to add and later remove set -x from the script file itself.

Step 4: enable strict error handling to catch failures early

set -euo pipefail

-e exits immediately on any command failure rather than silently continuing; -u treats referencing an undefined variable as an error; -o pipefail makes a pipeline fail if any stage fails, not just the last one — together, a common and genuinely useful “fail loudly and immediately” default for scripts.

Step 5: install and run ShellCheck

sudo apt install shellcheck    # Debian/Ubuntu
brew install shellcheck        # macOS

shellcheck myscript.sh

ShellCheck performs static analysis — checking the script’s source code without ever running it — catching an entire category of well-known bugs (unquoted expansions, common globbing mistakes, unreachable code) before they ever have a chance to manifest against real input.

Step 6: understand ShellCheck’s specific warning codes

shellcheck myscript.sh
# SC2086: Double quote to prevent globbing and word splitting

Each ShellCheck warning has a specific numbered code (SC2086, in this example) — searching that exact code in ShellCheck’s own documentation gives a detailed explanation and a recommended fix, rather than needing to guess at what a shorter inline warning message means.

Step 7: integrate ShellCheck into your editor for immediate feedback

Most popular editors (VS Code, vim, Neovim) have a
  ShellCheck plugin/extension providing inline warnings
  as you write, rather than only when explicitly run

Catching an issue as you type is faster to fix than discovering it later via a separate manual shellcheck run — worth the small one-time setup effort for any shell scripting done regularly.

Step 8: combine both tools in your actual debugging workflow

1. Run shellcheck first — fix anything it flags before
   the script has even executed once
2. If a bug remains, add set -x (or run with bash -x)
   to trace exactly what the script does at runtime

ShellCheck catches structural, pattern-based issues; set -x reveals actual runtime behavior — using both together, in this order, catches the widest range of bugs with the least wasted effort.

Why these two tools cover genuinely different failure categories

ShellCheck catches bugs that are visible from the script’s source code alone — a missing quote, a common typo pattern — regardless of what input the script ever receives; set -x reveals bugs that only manifest with specific runtime data, showing you the actual expanded commands rather than the source code you wrote. Neither tool substitutes for the other; together, they cover most of what makes shell scripts hard to debug otherwise.