Shell Scripting Basics
Introduction
Repeating the same commands for backup, deploy, and health checks wastes time and invites typos. Bash scripts automate those steps. This chapter covers your first executable script, variables, conditionals, loops, functions, and safe defaults—enough to glue together curl, tar, and systemctl from earlier chapters.
Prerequisites
- Shell and Bash basics
- Networking basics (
curlexamples)
Your First Script
Create hello.sh:
#!/usr/bin/env bash
# Greet the user running the script
echo "Hello, $(whoami). Today is $(date +%Y-%m-%d)."Make executable and run:
chmod +x hello.sh
./hello.shShebang #!/usr/bin/env bash finds bash on PATH—portable across distros.
Run without execute bit:
bash hello.shPositional Parameters
args.sh:
#!/usr/bin/env bash
# Script name is $0; first argument is $1
echo "Script: $0"
echo "Argument count: $#"
echo "All args: $@"
echo "First arg: $1"chmod +x args.sh
./args.sh foo bar| Variable | Meaning |
|---|---|
$0 | Script path |
$1, $2 | Arguments |
$# | Count |
$@ | All args as list |
$? | Exit code of last command |
Variables and Command Substitution
Conditionals
File and string tests with [[ ]] (bash):
Common tests:
| Test | True when |
|---|---|
-f path | Regular file exists |
-d path | Directory exists |
-z "$s" | String empty |
-n "$s" | String non-empty |
Numeric compare inside (( )):
COUNT=3
if (( COUNT > 0 )); then
echo "positive"
fiChain commands:
# Run step2 only if step1 succeeds
./build.sh && ./deploy.sh
# Run cleanup even if deploy fails
./deploy.sh || echo "Deploy failed"Loops
#!/usr/bin/env bash
# Loop over arguments
for host in prod staging; do
echo "Checking $host"
ssh "$host" 'hostname'
done
# C-style loop
for i in {1..3}; do
echo "Attempt $i"
doneWhile read lines from a file:
#!/usr/bin/env bash
while IFS= read -r line; do
echo "Line: $line"
done < servers.txtFunctions
local keeps variables inside the function.
Safer Scripts: set -euo pipefail
Add near the top of production scripts:
#!/usr/bin/env bash
set -euo pipefail| Option | Effect |
|---|---|
-e | Exit on first failing command |
-u | Error on unset variables |
-o pipefail | Pipeline fails if any stage fails |
Use set +e temporarily when you expect a command to fail and handle $? yourself.
Practical Example: Backup Logs
backup-logs.sh:
#!/usr/bin/env bash
set -euo pipefail
SRC=/var/log/myapp
DEST=/backup
STAMP=$(date +%Y%m%d-%H%M%S)
ARCHIVE="$DEST/myapp-logs-$STAMP.tar.gz"
mkdir -p "$DEST"
tar -czf "$ARCHIVE" -C "$(dirname "$SRC")" "$(basename "$SRC")"
echo "Created $ARCHIVE"Schedule with cron.
Practical Example: HTTP Health Check
health.sh:
#!/usr/bin/env bash
set -euo pipefail
URL=${1:-http://127.0.0.1:8080/health}
if curl -sf "$URL" | grep -q '"status":"UP"'; then
echo "Health OK: $URL"
exit 0
fi
echo "Health check failed: $URL" >&2
exit 1chmod +x health.sh
./health.sh http://127.0.0.1:8080/actuator/healthWire into cron or a deploy script: run after systemctl restart myapp.
Practical Example: Restart App Wrapper
restart-myapp.sh:
#!/usr/bin/env bash
set -euo pipefail
sudo systemctl restart myapp
sleep 3
./health.shKeeps systemd as source of truth; script only orchestrates.
FAQ
#!/bin/bash vs #!/usr/bin/env bash?
env finds bash wherever it lives—better for mixed distros and macOS/Linux shared scripts.
Script says Permission denied?
chmod +x or run with bash script.sh. Windows line endings (\r) break shebang—use dos2unix script.sh.
Why quote "$var"?
Unquoted $var word-splits on spaces and expands globs—classic bug.
ShellCheck?
Install shellcheck script.sh for lint hints before production.