Formatting Bash with shfmt
Consistent formatting reduces review noise and makes shell control flow easier to scan. Formatting does not prove correctness, but it provides a stable base for linting and testing.
Learning objectives
By the end of this lesson
- Check format without rewriting.
- Apply one shared style.
- Align editors and CI.
- Handle generated and embedded content deliberately.
- Control formatter version changes.
1. shfmt standardizes shell structure
shfmt parses shell source and rewrites it into a consistent style. This reduces formatting debates and makes control flow easier to review.
flowchart LR S["source"] --> P["shfmt parser"] P --> F["canonical format"] F --> D["diff or rewrite"]
2. Use diff mode in CI
shfmt -d scripts/Diff mode checks formatting without changing repository files.
3. Use write mode locally
shfmt -w scripts/ci/build.shDevelopers can apply the standard automatically before committing.
4. Choose project flags once
SHFMT_FLAGS=(-i 2 -ci)
shfmt "${SHFMT_FLAGS[@]}" -d scripts/Use the same options in editors, pre-commit helpers, and CI to prevent inconsistent results.
5. Formatting is not correctness
shfmt does not prove your variable expansions are safe or your business logic is correct. Combine it with ShellCheck and behavioral tests.
6. Here-documents deserve attention
cat <<'EOF'
this is data
indentation may matter to the consumer
EOFA formatter preserves shell semantics, but the data embedded in a here-document may have its own formatting rules.
7. Generated files need a consistent policy
Either format generated shell output automatically or exclude it explicitly. Do not mix manual formatting fixes into files that will be regenerated.
8. Formatter versions can create broad diffs
Control shfmt version changes so repository-wide formatting updates are intentional and easy to review.
9. Local hooks are convenience, CI is enforcement
Pre-commit formatting shortens feedback loops, but contributors can bypass hooks. CI should remain the authoritative formatting gate.
10. Formatting does not erase platform differences
A consistently formatted script can still depend on GNU-only utilities or Bash-only syntax. Portability remains a separate engineering concern.
11. Hands-on lab: format a messy script
mkdir -p "$HOME/devops-academy/bash/chapter17/lesson03"
cd "$HOME/devops-academy/bash/chapter17/lesson03"
cat > messy.sh <<'EOF'
#!/usr/bin/env bash
if [[ -n ${CI:-} ]];then
echo "CI mode"
else
echo "local mode"
fi
for x in one two three;do echo "$x";done
EOF
if command -v shfmt >/dev/null 2>&1; then
shfmt -i 2 -ci -d messy.sh || true
shfmt -i 2 -ci -w messy.sh
cat messy.sh
else
printf 'shfmt not installed; inspect messy.sh manually.\n' >&2
fiVerification checklist
12. Knowledge check
Question 1. What does shfmt -d do?
Question 2. Does shfmt replace ShellCheck?
Question 3. Why control formatter versions?
Question 4. Should CI verify formatting even when editors auto-format?
13. Summary
shfmt makes shell layout consistent and reviewable. Define one style, check it in CI, apply it locally, and treat it as a complement to linting and tests rather than a correctness tool.
14. Further reading
- shfmt documentation.
- mvdan/sh shell parser documentation.
- GNU Bash Reference Manual — compound commands and here-documents.
- ShellCheck documentation.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.