Study Projects
Automated Backup
A backup script must be safe: it should refuse to run twice at once, timestamp its archives, keep a rotation, and clean up after itself. This example shows every one of those habits.
#!/usr/bin/env bash
set -euo pipefail
readonly SRC="${1:?usage: backup.sh SOURCE_DIR [DEST_DIR]}"
readonly DEST="${2:-$HOME/backups}"
readonly KEEP=7
readonly STAMP="$(date +%Y%m%d-%H%M%S)"
readonly NAME="$(basename "$SRC")"
[[ -d "$SRC" ]] || { echo "no such directory: $SRC" >&2; exit 1; }
mkdir -p "$DEST"
# One archive per run; tar streams the tree.
archive="$DEST/${NAME}-${STAMP}.tar.gz"
tar -czf "$archive" -C "$(dirname "$SRC")" "$NAME"
echo "created $archive ($(du -h "$archive" | cut -f1))"
# Rotation: keep the newest N archives.
find "$DEST" -maxdepth 1 -name "${NAME}-*.tar.gz" -type f \
| sort | head -n -"$KEEP" | xargs -r rm -f
echo "kept the last $KEEP archives"
System Report
A report script gathers facts, formats them, and writes a file that a human can scan. Note how it degrades gracefully when a tool is missing.
#!/usr/bin/env bash
set -euo pipefail
have() { command -v "$1" >/dev/null 2>&1; }
section() { printf '\n== %s ==\n' "$1"; }
section "Host"
echo "name : $(uname -n)"
echo "os : $(uname -s) $(uname -r)"
echo "user : ${USER:-unknown}"
echo "uptime: $(uptime -p 2>/dev/null || uptime)"
section "CPU & Memory"
if have nproc; then echo "cores: $(nproc)"; fi
if [[ -r /proc/meminfo ]]; then
awk '/MemTotal|MemAvailable/ { printf "%-16s %s %s\n", $1, $2, $3 }' /proc/meminfo
fi
section "Disk"
df -h --output=target,size,used,avail,pcent 2>/dev/null | head -10
section "Top processes"
ps -eo pid,comm,%cpu,%mem --sort=-%cpu 2>/dev/null | head -6
echo
echo "report finished at $(date)"
Interactive Menu
Bash has a built-in menu generator, select. Combined with a case, it makes a tidy admin tool in a few lines.
#!/usr/bin/env bash
set -euo pipefail
main() {
PS3="choose an option: " # the prompt select prints
select opt in "Show date" "List files" "Disk usage" "Quit"; do
case "$opt" in
"Show date") date ;;
"List files") ls -lh ;;
"Disk usage") df -h . ;;
"Quit") echo "bye"; break ;;
*) echo "invalid choice: $REPLY" >&2 ;;
esac
done
}
main "$@"
Service Health Check
A health check probes endpoints, retries with a bounded loop, and exits non-zero when something is down — exactly what a monitoring system expects.
#!/usr/bin/env bash
set -euo pipefail
readonly URL="${1:?usage: health.sh URL}"
readonly TRIES=5
readonly DELAY=2
for (( attempt = 1; attempt <= TRIES; attempt++ )); do
if curl -sf --max-time 5 "$URL" >/dev/null; then
echo "OK $URL (attempt $attempt)"
exit 0
fi
echo "FAIL $URL (attempt $attempt/$TRIES)" >&2
(( attempt < TRIES )) && sleep "$DELAY"
done
echo "UNHEALTHY: $URL did not respond after $TRIES attempts" >&2
exit 1
Run it from cron or a systemd timer; a non-zero exit is all the alarm you need.
Project Bootstrap
A bootstrap script creates a reproducible skeleton. Make it idempotent — running it twice must not fail or duplicate work.
#!/usr/bin/env bash
set -euo pipefail
readonly ROOT="${1:-myapp}"
mkdir -p "$ROOT"/{src,tests,docs,scripts}
[[ -f "$ROOT/README.md" ]] || cat > "$ROOT/README.md" <<'EOF'
# myapp
Created by bootstrap.sh
EOF
# A git repository only if there is not one already.
if [[ ! -d "$ROOT/.git" ]]; then
git -C "$ROOT" init -q
printf 'created git repository in %s\n' "$ROOT"
fi
printf 'tree:\n'
find "$ROOT" -maxdepth 2 | sort
Next Steps
- Add
shellcheckto your editor and to CI. - Turn each project above into a testable function and cover it with
bats. - Read other people's scripts — starting with your system's
/etc/init.dand/usr/binhelpers. - Browse the curated links on the References & Tools page.