Skip to content
C.W.K.
Stream
Lesson 12 of 12 · published

Practical Scripts: backup, deploy, daily-check

~13 min · practical, backup, deploy

Level 0Window Tourist
0 XP0/95 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

The shape of a reviewable shell script

Useful scripts make their interpreter, inputs, state changes, functions, main flow, and cleanup limits visible. Treat the examples as sketches to verify against a real environment, not production recipes to paste unchanged.

backup.sh — rsync to dated archive

#!/usr/bin/env bash
set -euo pipefail
src="${1:-$HOME}"
dest="${BACKUP_ROOT:-/Volumes/backup}/$(date +%Y/%m/%d)"
mkdir -p "$dest"
rsync -a --dry-run --itemize-changes --exclude='.cache' "$src/" "$dest/"
printf 'review the dry run, then repeat without --dry-run\n'
# Add --delete only when the destination is a verified disposable mirror.

deploy.sh — test then push

#!/usr/bin/env bash
set -euo pipefail
log() { printf '%(%H:%M:%S)T %s\n' -1 "$*"; }
trap 'log "deploy failed at line $LINENO"' ERR
log 'running tests'
./test.sh
log 'pushing'
git push origin main
log 'done'

daily-check.sh — health gate

#!/usr/bin/env bash
set -u
set -o pipefail
fail=0
check_http() { curl --fail --silent --show-error --max-time 10 https://api.example/health; }
check_worker() { pgrep -x my-worker >/dev/null; }
check_disk() { df -P / | awk 'NR==2 { exit !(\$5+0 < 90) }'; }
for name in http worker disk; do
  if "check_${name}"; then
    printf 'OK %s\n' "$name"
  else
    printf 'FAIL %s\n' "$name" >&2
    fail=1
  fi
done
exit "$fail"

Assign ownership before scheduling

Keep one purpose per script and record the runtime owner, interpreter, inputs, outputs, logs, retry policy, and stop procedure beside it. A readable file is only one part of an operable job.

Name the purpose and lifetime

A purpose-named file and an ownership note make a script easier to find, stop, and replace. Date-prefixed scratch names hide whether a job is temporary or persistent.

Put verification between example and operation

Preview source and destination, permissions, and deletion behavior before turning a teaching sketch into a scheduled command. A dry run is evidence to review, not ceremonial output.

Use functions and argument arrays instead of eval

Store data as data. Named functions and quoted arrays preserve argument boundaries; eval reparses strings as shell syntax and turns untrusted text into commands.

Code

Reusable header block·bash
#!/usr/bin/env bash
# @ops: persistent | weekly backup of ~ to NAS
set -euo pipefail
IFS=$'\n\t'
HERE="$(cd "$(dirname "$0")" && pwd)"
log() { printf '%(%H:%M:%S)T %s\n' -1 "$*" >&2; }
die() { log "FATAL: $*"; exit 1; }
trap 'die "failed at line $LINENO"' ERR
# real work below
log 'starting'

External links

Exercise

Save the reusable header block as ~/bin/_template.sh. Use it to write a tiny backup.sh that rsyncs $HOME/notes/ to ~/Backups/notes-$(date +%F). Run it. Confirm it works under set -euo pipefail.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.