LFCS 128 ๐ง Shell Scripting โ Debugging
A script that fails silently or behaves unexpectedly is a script that needs debugging. The shell provides three mechanisms for this: set -x prints each command before executing it, set -v prints each line before parsing it, and the -n flag parses the script without executing it. Beyond these, there are the PS4 prompt customization, the trap DEBUG for per-command breakpoints, the BASH_XTRACEFD for redirecting trace output, and the shellcheck static analyzer for finding problems before the script runs. The LFCS exam tests the basic mechanisms and the syntax of the set flags. The critical point is that set -x is noisy and slows the script, so it should be enabled for a section of the script, not for the entire run, or redirected to a file for later analysis.
Key point: set -x prints each command before executing it. set +x disables tracing. bash -x script.sh traces the entire script from the command line. The PS4 variable controls the trace prompt. The trap DEBUG command runs a handler before each command, which is useful for logging. The set -n (or bash -n) parses the script without executing, which is the syntax check.
Why debugging requires multiple mechanisms
The silent failure problem. A script that exits with 0 but does not perform the expected operation is the hardest failure to diagnose. The set -x trace shows exactly which commands ran and which did not. The set -e strict mode turns silent failures into visible exits. The combination is the standard debugging workflow.
The trace noise problem. A script with a thousand commands produces a thousand trace lines. The output is unreadable and the relevant section is buried. The solution is to enable set -x for the specific function or block, disable it with set +x after, and use PS4 to add context to each trace line.
The parsing problem. A script with a syntax error does not run at all, but the error message from the shell is often unhelpful. The set -n or bash -n script.sh parses the script without executing, reporting syntax errors before the first command runs. This is the first step in any debugging session.
The static analysis problem. Some bugs are visible without running the script: unquoted variables, undefined variables, unused arguments, deprecated syntax. The shellcheck tool parses the script and reports these issues. It is the static analyzer for shell scripts, and it catches the common errors that the runtime trace does not reveal until the specific path is executed.
The trade-off. The runtime trace is precise but verbose. The static analysis is fast but does not catch logic errors. The trap DEBUG is useful but adds overhead. The exam tests the syntax and the use cases for each mechanism.
a. The set flags and the trace output
The set -x flag enables tracing. The set +x flag disables it. The trace output goes to stderr by default.
#!/bin/bash
echo "Start"
set -x
result=$((3 + 4))
echo "Result: $result"
set +x
echo "End"
The output shows each command prefixed with the PS4 prompt (default +).
Start
+ result=7
+ echo 'Result: 7'
Result: 7
End
The set -v flag prints each line before parsing, which is slightly different from set -x. The set -v shows the raw line, including comments and variable references before expansion. The set -x shows the command after expansion, which is what actually runs.
#!/bin/bash
set -v
echo "Hello, $USER"
The set -v output shows echo "Hello, $USER" (the raw line). The set -x output shows echo 'Hello, alice' (the expanded line). The difference matters when debugging variable expansion.
The bash -x script.sh command enables tracing for the entire script without modifying the script. The bash -n script.sh command parses without executing.
# Trace the entire script
bash -x script.sh
# Syntax check
bash -n script.sh
b. The PS4 prompt and the BASH_XTRACEFD
The PS4 variable controls the trace prompt. The default is +. It can be set to include the line number, the function name, the file name, and the timestamp.
#!/bin/bash
export PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
set -x
my_func() {
echo "Inside"
}
my_func
The output shows the file, line, and function name for each traced command.
+script.sh:5:: my_func
+script.sh:3:my_func: echo Inside
Inside
The ${BASH_SOURCE} is the script file. The ${LINENO} is the current line. The ${FUNCNAME[0]} is the current function. The [-1] would be the bottom of the call stack, and [0] is the top.
The BASH_XTRACEFD variable redirects the trace output to a file descriptor. The default is 2 (stderr). Setting it to a custom file descriptor sends the trace to a separate file, which is useful for long-running scripts or for CI pipelines where the trace should not be mixed with the normal output.
#!/bin/bash
exec 5> /tmp/trace.log
export BASH_XTRACEFD=5
set -x
echo "This is not traced"
echo "This is traced"
The exec 5> /tmp/trace.log opens file descriptor 5 for writing. The BASH_XTRACEFD=5 sends the trace to that file descriptor. The echo "This is not traced" is executed before set -x and does not appear in the trace. The echo "This is traced" does.
c. The trap DEBUG and the shellcheck analyzer
The trap DEBUG command runs a handler before each command. The handler can log the command, inspect the variables, or implement a conditional breakpoint.
#!/bin/bash
trap 'echo "Executing: $BASH_COMMAND"' DEBUG
echo "First"
echo "Second"
The $BASH_COMMAND variable holds the command that is about to be executed. The handler runs before each command, including the echo commands. The output is:
Executing: echo "First"
First
Executing: echo "Second"
Second
The shellcheck tool is a static analyzer. It is installed separately (apt install shellcheck or brew install shellcheck). It reads the script and reports issues with line numbers and explanations.
shellcheck script.sh
The output is a list of findings. The most common findings are:
SC2086: Double quote to prevent globbing and word splitting.SC2154: Variable is referenced but not assigned.SC2164: Usecd ... || exitin casecdfails.SC2046: Quote this to prevent word splitting.
The -s sh flag tells shellcheck to target POSIX shell instead of bash. The -e flag excludes specific codes. The -f flag sets the output format (gcc, json, checkstyle).
# POSIX shell
shellcheck -s sh script.sh
# Ignore a specific code
shellcheck -e SC2086 script.sh
The shellcheck is the static analysis, the set -x is the runtime trace, and the bash -n is the syntax check. Each catches a different class of problem.
Complete Example Session
# ============================================
# PART 1: SET -X TRACE
# ============================================
cat > trace.sh <<'EOF'
#!/bin/bash
echo "Start"
set -x
result=$((3 + 4))
echo "Result: $result"
set +x
echo "End"
EOF
chmod +x trace.sh
./trace.sh
# Output:
# Start
# + result=7
# + echo 'Result: 7'
# Result: 7
# End
# ============================================
# PART 2: BASH -X
# ============================================
bash -x trace.sh
# Traces the entire script.
# ============================================
# PART 3: SYNTAX CHECK
# ============================================
cat > broken.sh <<'EOF'
#!/bin/bash
if [ -f /etc/passwd ]; then
echo "Found"
EOF
bash -n broken.sh
# Output: broken.sh: line 5: syntax error: unexpected end of file
# ============================================
# PART 4: PS4 PROMPT
# ============================================
cat > ps4.sh <<'EOF'
#!/bin/bash
export PS4='+${BASH_SOURCE}:${LINENO}: '
set -x
echo "Hello"
EOF
chmod +x ps4.sh
./ps4.sh
# Output:
# +ps4.sh:4: echo Hello
# Hello
# ============================================
# PART 5: TRACE TO FILE
# ============================================
cat > tracefile.sh <<'EOF'
#!/bin/bash
exec 5> /tmp/trace.log
export BASH_XTRACEFD=5
set -x
echo "Traced"
EOF
chmod +x tracefile.sh
./tracefile.sh
cat /tmp/trace.log
# Output: + echo Traced
# ============================================
# PART 6: TRAP DEBUG
# ============================================
cat > debugtrap.sh <<'EOF'
#!/bin/bash
trap 'echo "Executing: $BASH_COMMAND"' DEBUG
echo "First"
echo "Second"
EOF
chmod +x debugtrap.sh
./debugtrap.sh
# Output:
# Executing: echo "First"
# First
# Executing: echo "Second"
# Second
# ============================================
# PART 7: SHELLCHECK
# ============================================
cat > unquoted.sh <<'EOF'
#!/bin/bash
files="*.txt"
echo $files
EOF
shellcheck unquoted.sh
# Output: SC2086 (info): Double quote to prevent globbing and word splitting.
# ============================================
# PART 8: SHELLCHECK IGNORE
# ============================================
shellcheck -e SC2086 unquoted.sh
# No output: the warning is suppressed.
# ============================================
# PART 9: CONDITIONAL TRACE
# ============================================
cat > condtrace.sh <<'EOF'
#!/bin/bash
if [ "$1" = "--debug" ]; then
set -x
fi
echo "Processing"
EOF
chmod +x condtrace.sh
./condtrace.sh --debug
# Output:
# + echo Processing
# Processing
The nine parts trace the debugging: set -x, bash -x, syntax check, PS4, trace to file, trap DEBUG, shellcheck, ignored codes, and conditional trace.
Quick Reference
set Flags
| Flag | Purpose |
|---|---|
set -x | Trace commands |
set +x | Disable trace |
set -v | Trace lines (raw) |
set -n | Parse without executing |
Command-Line Flags
| Flag | Purpose |
|---|---|
bash -x script.sh | Trace entire script |
bash -n script.sh | Syntax check |
PS4 Components
| Component | Meaning |
|---|---|
$BASH_SOURCE | Script file |
$LINENO | Line number |
$FUNCNAME[0] | Current function |
$BASH_COMMAND | Command being executed |
Trace Redirect
| Variable | Purpose |
|---|---|
BASH_XTRACEFD | File descriptor for trace |
exec N> file | Open file descriptor |
shellcheck Flags
| Flag | Purpose |
|---|---|
-s sh | POSIX shell target |
-e SC2086 | Ignore specific code |
-f json | Output format |
Best Practices
โ Do This:
set -x
<!-- Enable trace for the section that is failing --> # โ
bash -n script.sh
<!-- Syntax check before running --> # โ
shellcheck script.sh
<!-- Static analysis in CI --> # โ
โ Don’t Do This:
# Enable set -x for the entire script
<!-- The output is unreadable --> # โ
# Ignore shellcheck findings
<!-- They are the common bugs --> # โ
# Assume no trace means no bug
<!-- Silent failures are invisible --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Trace unreadable | No PS4 customization | Add $LINENO and $FUNCNAME |
| Trace in stdout | BASH_XTRACEFD not set | Redirect to file descriptor |
| Syntax error hidden | Script runs and fails silently | bash -n |
| Unquoted variable warning | No shellcheck | Run shellcheck |
| Trace continues after error | set -e not set | Combine set -ex |
Real-World Examples
1. Trace Section
set -x
code_to_debug
set +x
2. Trace Entire Script
bash -x script.sh
3. Syntax Check
bash -n script.sh
4. Custom PS4
export PS4='+${BASH_SOURCE}:${LINENO}: '
5. Trace to File
exec 5> /tmp/trace.log
export BASH_XTRACEFD=5
6. Trap Debug
trap 'echo "$BASH_COMMAND"' DEBUG
7. Shellcheck
shellcheck script.sh
8. Ignore Code
shellcheck -e SC2086 script.sh
9. Conditional Trace
[ "$DEBUG" = "1" ] && set -x
10. Verbose Trace
set -v
Visual
Trace Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ set -x โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Each command printed to stderr โ
โ Prefixed with PS4 prompt โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ set +x โ
โ Trace stops โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Debugging Levels
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ bash -n script.sh โ
โ โ Syntax check โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ shellcheck script.sh โ
โ โ Static analysis โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ bash -x script.sh โ
โ โ Runtime trace โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Trace commands | set -x |
| Trace lines | set -v |
| Disable trace | set +x |
| Parse only | set -n or bash -n |
| Trace script | bash -x |
| Prompt control | PS4 |
| Trace redirect | BASH_XTRACEFD |
| Per-command trap | trap DEBUG |
| Static analysis | shellcheck |
Key takeaways:
set -xis the runtime trace. It prints each command before executing. Theset +xdisables it. The trace goes to stderr by default.bash -nis the syntax check. It parses the script without executing, reporting syntax errors before the first command runs.PS4andBASH_XTRACEFDmake the trace readable. ThePS4includes the line number and function name. TheBASH_XTRACEFDredirects the trace to a separate file.trap DEBUGruns a handler before each command. The$BASH_COMMANDvariable holds the command. The handler is useful for logging or conditional breakpoints.shellcheckis the static analyzer. It catches unquoted variables, undefined variables, and other common mistakes before the script runs. It is the first step in any debugging session.
Remember: Debugging is a layered process. The syntax check is first. The static analysis is second. The runtime trace is third. The set -x shows what runs. The set -v shows what is parsed. The bash -n shows what is broken. The shellcheck shows what is wrong. The trap DEBUG shows what is next. The exam tests the flags and the syntax.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!