Bash Scripting — Writing Clean and Readable Code
Introduction
You’ve already learned how to write Bash scripts that work. Now it’s time to write scripts that are easy to read, easy to maintain, and easy to hand off to your teammates (or to your future self six months from now).
Clean code isn’t about being fancy — it’s about being kind to the next person who opens your script. That person might be you, debugging at 2 AM during an outage. Trust me, you’ll thank yourself.
Why Clean Code Matters
| Problem | Dirty Script | Clean Script |
|---|---|---|
| Debugging | “What does x even hold?” |
Variables tell you exactly what they store |
| Onboarding | New team member is lost | Code reads like documentation |
| Maintenance | One change breaks everything | Modular functions, easy to update |
| Reviews | PR takes hours to review | Reviewer understands intent in minutes |
What You’ll Learn in This Chapter
This chapter is split into focused lessons. Work through them in order:
| # | Topic | What You’ll Learn |
|---|---|---|
| 1 | Naming Conventions | How to name variables, functions, and scripts clearly |
| 2 | Commenting Best Practices | When and how to write useful comments |
| 3 | Script Header Block | A standard header template every script should have |
| 4 | Function Organization | Structuring scripts with functions and the main() pattern |
| 5 | ShellCheck Compliance | Catching bugs before they bite with static analysis |
| 6 | Practice Exercises | Hands-on exercises to cement everything |
The Golden Rules
Before you dive in, keep these five rules pinned to your wall:
- Name things clearly — If you need a comment to explain what a variable holds, rename it.
- Comment the why, not the what — The code shows what it does; explain why it does it.
- Every script gets a header — Author, purpose, usage — no exceptions.
- Functions over monoliths — Break logic into small, named functions.
- Run ShellCheck on every script — Treat warnings as errors.
A Before-and-After Example
Before — “It works, don’t touch it” style:
#!/bin/bash
d=$(date +%Y%m%d)
f=/tmp/bk_$d.tar.gz
tar czf $f /var/www
if [ $? -eq 0 ]; then
echo "ok"
else
echo "fail"
fi
After — Clean and readable:
#!/usr/bin/env bash
set -euo pipefail
# ---------------------------------------------------------------------------
# backup_website.sh — Create a compressed backup of the web root
# Author: ops-team | Last updated: 2025-06-01
# ---------------------------------------------------------------------------
readonly BACKUP_DIR="/tmp"
readonly SOURCE_DIR="/var/www"
readonly DATE_STAMP
DATE_STAMP="$(date +%Y%m%d)"
readonly BACKUP_FILE="${BACKUP_DIR}/website_backup_${DATE_STAMP}.tar.gz"
create_backup() {
echo "Creating backup of ${SOURCE_DIR}..."
tar czf "${BACKUP_FILE}" "${SOURCE_DIR}"
}
main() {
create_backup
echo "Backup complete: ${BACKUP_FILE}"
}
main "$@"
Both scripts do the same thing. Which one would you rather debug at 2 AM?
Let’s Get Started
Head to the first lesson: Naming Conventions →
In this chapter you’ll transform your scripts from “it works” to “it’s a pleasure to maintain.”