Posted on
Artificial Intelligence

Create Self-Documenting Bash Scripts with Artificial Intelligence

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Create Self-Documenting Bash Scripts with Artificial Intelligence

Ever joined a project where the Bash scripts were powerful but opaque—no --help, a stale README, and comments that don’t match the code anymore? What if scripts could explain themselves and keep their own documentation fresh automatically?

This post shows you how to build self-documenting Bash scripts and use AI to keep their help text, examples, and man pages in sync with the code—so your docs never drift again.


Why this matters

  • Documentation drift is real: CLI flags evolve faster than READMEs.

  • Bash scripts live a long time and get copied everywhere—help output is the one piece of documentation that always travels with the script.

  • AI can summarize code, infer usage and options, and write crisp examples… but only if you give it a predictable place to put the output.

  • With a little structure (and common Linux tools), you can keep docs up-to-date on every commit, even offline with local models.


What you’ll build

  • A simple, parseable header block inside your Bash scripts (source of truth)

  • An internal --help that prints this header

  • Automatic man page generation from --help

  • An AI-powered generator that updates the header from your code

  • Optional git hook to keep everything in sync


Prerequisites and installation

Install the common tools we’ll use:

  • git, curl, jq

  • shellcheck (lint)

  • shfmt (format)

  • help2man (man page generation)

  • pipx (to install Python CLIs in isolated environments)

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y git curl jq shellcheck shfmt help2man pipx
# If pipx isn’t packaged on your distro:
# sudo apt install -y python3-pip
# python3 -m pip install --user pipx
# python3 -m pipx ensurepath

Fedora/RHEL (dnf):

sudo dnf install -y git curl jq ShellCheck shfmt help2man pipx

openSUSE (zypper):

sudo zypper install -y git curl jq ShellCheck shfmt help2man pipx

AI options (pick one):

  • Local (no API key):

    curl -fsSL https://ollama.com/install.sh | sh
    # Fetch a small, general model
    ollama pull llama3.1
    
  • Cloud via a general-purpose CLI:

    pipx install llm
    # Then configure a provider/key per the llm docs (e.g., OpenAI, Anthropic, etc.)
    # Example for OpenAI (after setting OPENAI_API_KEY):
    # llm keys set openai
    

Tip: After installing pipx, you may need to open a new shell or run pipx ensurepath.


1) Add a parseable documentation header

Put a tightly structured header in your script that your code can print verbatim. Prefix doc lines with #% so they’re easy to extract.

Example: backup-s3 (skeleton)

#!/usr/bin/env bash
set -euo pipefail

#% BEGIN AI DOC
#% Name: backup-s3
#% Description: Sync a local directory to an S3 bucket with retries and logging.
#% Usage: backup-s3 [-n] [-r N] [-v] SRC_DIR S3_URI
#% Options:
#%   -n, --dry-run     Show what would be done without actually syncing
#%   -r, --retries N   Number of retries on failure (default: 3)
#%   -v, --verbose     Increase logging verbosity (repeatable)
#%   -h, --help        Show help and exit
#% Examples:
#%   backup-s3 -r 5 ./site s3://my-bucket/site
#%   backup-s3 --dry-run ./data s3://my-bucket/data
#% END AI DOC

usage() {
  # Print lines that start with "#% " (strip the marker)
  sed -n 's/^#% \{0,1\}//p' "$0"
}

dry_run=0 retries=3 verbose=0
while [[ $# -gt 0 ]]; do
  case "$1" in
    -n|--dry-run) dry_run=1; shift ;;
    -r|--retries) retries="${2:-}"; shift 2 ;;
    -v|--verbose) verbose=$((verbose+1)); shift ;;
    -h|--help) usage; exit 0 ;;
    --) shift; break ;;
    -*) echo "Unknown option: $1" >&2; usage; exit 2 ;;
    *) break ;;
  esac
done

# Guard required args
if [[ $# -lt 2 ]]; then
  echo "Error: missing SRC_DIR and S3_URI" >&2
  echo >&2
  usage >&2
  exit 2
fi

src="$1"; dest="$2"

# Your implementation here (e.g., using aws s3 sync).
# For demo purposes, we only echo the parsed intent.
if (( dry_run )); then echo "[DRY RUN]"; fi
echo "Would sync '$src' -> '$dest' with retries=$retries verbose=$verbose"

Now your script can always explain itself:

./backup-s3 --help

This header block is the one place that must remain correct—so let’s make AI maintain it for you.


2) Turn your header into --help and a man page

You already have usage() to print help. Next, generate a man page with help2man, which reads --help output:

help2man -N -n "Sync a local directory to S3 with retries" \
  -o man/backup-s3.1 ./backup-s3
  • -N disables “report bugs” footer

  • -n sets the short description

Distribute man/backup-s3.1 along with your script, or add a Makefile target to regenerate it on demand.


3) Use AI to update the header from the code

Create a tiny tool that asks an AI model to summarize your script into the #% header format and either print it or update in place.

Save as tools/gen-help:

#!/usr/bin/env bash
set -euo pipefail

script="${1:-}"
[[ -z "$script" || ! -f "$script" ]] && {
  echo "Usage: $0 <script.sh> [-i]" >&2
  exit 2
}

inplace=0
[[ "${2:-}" == "-i" ]] && inplace=1

read -r -d '' PROMPT <<'EOF'
You are generating a documentation header for a Bash script.
Output ONLY lines starting with "#% " using this template:
#% Name: <script-name>
#% Description: <one concise line>
#% Usage: <command and flags/args>
#% Options:
#%   -x, --long    <description>
#% Examples:
#%   <example 1>
#%   <example 2>
Keep it accurate, minimal, and consistent with the code.
EOF

generate() {
  # Prefer local (no key) if available
  if command -v ollama >/dev/null 2>&1; then
    { printf '%s\n\n' "$PROMPT"; cat "$script"; } | ollama run llama3.1
    return
  fi
  # Fallback to "llm" CLI (configured with your provider/key)
  if command -v llm >/dev/null 2>&1; then
    # llm reads prompt from stdin; -s sets a system message
    # We pass PROMPT as system and the code on stdin
    LLM_MODEL="${LLM_MODEL:-gpt-4o-mini}"
    cat "$script" | llm -m "$LLM_MODEL" -s "$PROMPT"
    return
  fi
  echo "No AI CLI found. Install one of: ollama, llm" >&2
  exit 1
}

out="$(generate | sed -e '/^```/d')"

if (( inplace )); then
  awk -v hdr="$out" '
    BEGIN{placed=0}
    /^#% BEGIN AI DOC/ {print; print hdr; skip=1; next}
    /^#% END AI DOC/   {skip=0; placed=1; next}
    !skip {print}
    END{
      if(!placed){
        print "#% BEGIN AI DOC"
            print hdr
        print "#% END AI DOC"
      }
    }
  ' "$script" > "$script.tmp"
  mv "$script.tmp" "$script"
  echo "Updated header in $script"
else
  printf '%s\n' "$out"
fi

Make it executable:

chmod +x tools/gen-help

Usage:

  • Preview docs: tools/gen-help ./backup-s3

  • Update in place: tools/gen-help ./backup-s3 -i

Notes:

  • For local inference, ollama pull llama3.1 first.

  • For cloud, install pipx install llm, set a provider and key, and optionally export LLM_MODEL=gpt-4o-mini (or a model you have access to).


4) Add guardrails: format, lint, and refresh docs pre-commit

Keep your scripts healthy and your docs current by wiring this into git.

Create .git/hooks/pre-commit:

#!/usr/bin/env bash
set -euo pipefail

changed=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.sh$' || true)
[[ -z "$changed" ]] && exit 0

echo "[pre-commit] Formatting with shfmt..."
shfmt -w ${changed}

echo "[pre-commit] Linting with shellcheck..."
for f in ${changed}; do shellcheck "$f"; done

echo "[pre-commit] Refreshing AI docs..."
for f in ${changed}; do ./tools/gen-help "$f" -i; done

git add ${changed}

Then:

chmod +x .git/hooks/pre-commit

Every commit will now:

  • Reformat with shfmt

  • Lint with shellcheck

  • Rebuild the #% header using your chosen AI tool

This keeps your help text aligned with your actual flags and behavior.


5) Optional: Regenerate man pages and README

Add simple Make targets you can run locally or in CI:

# Makefile
SCRIPTS := backup-s3
MAN_DIR := man

.PHONY: help docs man
help:
    @echo "make docs    - refresh AI headers"
    @echo "make man     - build man pages"

docs:
    @for s in $(SCRIPTS); do ./tools/gen-help $$s -i; done

man:
    @mkdir -p $(MAN_DIR)
    @for s in $(SCRIPTS); do \
      help2man -N -n "$$s manual" -o $(MAN_DIR)/$$s.1 ./$$s ; \
    done

Run:

make docs
make man

You can also generate a README section from --help:

./backup-s3 --help | sed '1s/^/# /; 2,$$s/^/    /' > README.md

Real-world tips

  • Keep the header minimal. Your --help should fit one terminal screen.

  • Prefer short flags with long equivalents (-r, --retries) and show defaults explicitly.

  • Ask AI for 1–2 realistic examples; delete anything you can’t test or guarantee.

  • Trust but verify: AI can be confidently wrong. Run the script with sample inputs and confirm help accuracy.

  • For private codebases, prefer local models via Ollama or on-prem providers.


Conclusion and next steps

Self-documenting Bash scripts reduce onboarding friction, prevent drift, and make your tooling discoverable. Add a parseable header, print it with --help, wire up help2man, and let AI keep that header up to date as you refactor.

Your next steps: 1) Add the #% header and usage() to one script you use daily. 2) Install the tooling: - apt: sudo apt install -y git curl jq shellcheck shfmt help2man pipx - dnf: sudo dnf install -y git curl jq ShellCheck shfmt help2man pipx - zypper: sudo zypper install -y git curl jq ShellCheck shfmt help2man pipx 3) Choose an AI path: - Local: curl -fsSL https://ollama.com/install.sh | sh && ollama pull llama3.1 - Cloud: pipx install llm and set a provider/key 4) Drop in tools/gen-help, run ./tools/gen-help your-script.sh -i 5) Add the pre-commit hook to keep everything fresh

Make your scripts your own documentation—so teammates (and future you) can just run --help and get to work.