Debugging Scripts
Trace Execution
bash -x script.sh arg1 arg2
This prints commands after expansion, which helps reveal wrong paths or unexpected values.
Trace Only a Section
set -x
important_command "$value"
set +x
Make Trace Output Useful
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x script.sh
Print Shell-Safe Values
printf 'path=%q\n' "$path"
%q shows how Bash would quote the value.
Reduce the Problem
When a script fails, create a minimal reproduction:
- Copy only the failing command and its required variables
- Replace real paths with a temporary sandbox
- Print inputs before the failing command
- Check the exit status immediately after the command
Check Syntax Without Running
bash -n script.sh
This catches syntax errors but does not validate runtime logic.
Recommended Static Analysis
If available, use ShellCheck:
shellcheck script.sh
ShellCheck catches many quoting, portability, and error-handling issues.
Common Debug Fixes
| Symptom | Likely Fix |
|---|---|
| Works manually, fails in cron | Set PATH, use absolute paths, log stderr |
| Fails on spaces in filenames | Quote variables and paths |
| Function changes global variable unexpectedly | Use local |
| Pipeline succeeds despite failed command | Enable set -o pipefail |
What's Next
Server Environment Context
This lesson matters in server operations because Debugging Scripts supports structured diagnosis, debug traces, and server triage without destructive guesses. On a workstation, a mistake may affect one project. On a server, the same mistake can interrupt users, hide evidence, weaken access control, or make recovery harder.
Use the commands in this lesson with three questions in mind:
- What system state am I about to inspect or change?
- What evidence should I capture before changing it?
- How will I prove the server is healthier after the command runs?
Operational Runbook Pattern
Use this repeatable pattern when applying the lesson on a real host:
| Phase | Goal | Bash Habit |
|---|---|---|
| Identify | Confirm host, user, and scope | hostname, id, pwd |
| Inspect | Read state before modifying it | systemctl status, ls -la, ss -tulpn |
| Change | Make the smallest safe change | Quote paths and prefer explicit options |
| Verify | Confirm the intended result | Check exit status, logs, and service health |
| Record | Leave a useful audit trail | Save command output or ticket notes |
Example session header:
printf 'time=%s host=%s user=%s cwd=%s
' "$(date -Is)" "$(hostname)" "$(id -un)" "$(pwd)"
Pre-Flight Checklist
Before running commands from this lesson on a production server, check:
- You are connected to the intended host.
- You know whether the command is read-only or state-changing.
- You have a rollback or recovery path for state-changing work.
- You understand whether
sudois required and why. - You have captured current service, disk, or network state if the work is risky.
Useful pre-flight commands:
hostnamectl 2>/dev/null || hostname
id
uptime
systemctl --failed 2>/dev/null || true
Production Safety Notes
| Risk | Safer Practice |
|---|---|
| Running on the wrong host | Print hostname and environment name first |
| Accidentally expanding paths | Quote variables: "$path" |
| Losing evidence | Copy logs or capture journalctl output before cleanup |
| Silent failure | Use set -euo pipefail in scripts and check exit codes interactively |
Over-broad sudo usage | Run the smallest command possible with elevated permissions |
When a command can delete, overwrite, restart, reload, or reconfigure something, do a dry run or read-only inspection first.
Validation Commands
After applying the technique from this lesson, validate with commands appropriate to the changed area:
printf 'exit_status=%s
' "$?"
systemctl --failed 2>/dev/null || true
journalctl -p warning -n 50 --no-pager 2>/dev/null || true
df -h
ss -tulpn 2>/dev/null || true
For application-facing changes, add an endpoint or process check:
curl -fsS http://127.0.0.1:8080/health >/dev/null || true
ps -eo pid,cmd,%cpu,%mem --sort=-%cpu | head
Automation Example
The following template shows how to turn this lesson into a repeatable server check. Adapt names and commands before using it.
#!/usr/bin/env bash
set -euo pipefail
log() {
printf '%s INFO %s
' "$(date -Is)" "$*" >&2
}
die() {
printf '%s ERROR %s
' "$(date -Is)" "$*" >&2
exit 1
}
run_02_debugging_scripts_check() {
log 'running Debugging Scripts validation'
hostname >/dev/null
uptime >/dev/null
}
run_02_debugging_scripts_check "$@"
Troubleshooting Flow
If the expected result does not appear, diagnose in this order:
- Confirm the command ran on the correct host and shell.
- Check whether the command failed with a non-zero exit status.
- Re-run the read-only inspection command with more explicit paths or options.
- Check recent logs for permission, path, DNS, disk, or service errors.
- Undo only the specific change you made, not unrelated user or system changes.
Useful debug commands:
set -x
# repeat the smallest failing command here
set +x
printf 'PATH=%s
' "$PATH"
type command 2>/dev/null || true
Practice Lab
Use a non-production VM, container, or temporary directory for practice:
- Capture a baseline using
date -Is,hostname,uptime, anddf -h. - Apply the main command pattern from Debugging Scripts to a safe test target.
- Intentionally trigger one harmless failure, such as a missing file or inactive service.
- Capture the error message and explain what Bash exit status it produced.
- Convert the manual check into a small script with logging and validation.
Review Questions
- Which commands in Debugging Scripts are read-only, and which can change server state?
- What is the safest way to test the command before using it on production data?
- What log, service, or health check proves the operation succeeded?
- What rollback step would you use if the result is wrong?
- Which parts of the process should be automated, and which should remain manual?
Field Notes
Server work rewards boring, explicit commands. Prefer commands that can be pasted into a runbook, reviewed by another operator, and repeated during an incident without relying on memory.
Keep lesson examples as starting points, not blind copy-paste snippets. Adjust paths, service names, package names, ports, and users to match the actual server environment.