Shell Scripting Fundamentals¶
Overview¶
“Write a small script” sounds scary until you see it is mostly saved commands with safety rails. Good Bash habits surface errors early instead of hiding them on production hosts.
Plain problem: A midnight cron script fails silently — no set -e, errors swallowed, exit code always 0. Monitoring thinks all is well. Good scripts fail loudly and leave evidence.
This page teaches the operations minimum: shebang, set -euo pipefail, arguments, exit codes, and readable output. Full Bash curriculum lives in the Shell Scripting track.
This is a Command Line tutorial in the REBASH Academy Linux for Cloud & DevOps Engineers series.
Prerequisites¶
- Ubuntu practice VM or WSL2
- Essential Linux Commands
- Text editor (nano, vim, or VS Code)
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain what a shell script is in plain language
- Write a script with shebang and safe mode flags
- Use positional arguments (
$1,$#) and exit codes - Build a small host-check script that fails clearly
- Break a script on purpose and fix it
- Answer fresher interview questions on Bash scripting basics
Architecture¶
You write a text file → mark executable → shell reads lines top to bottom → each command returns an exit code → caller (cron/systemd/human) sees success or failure.
Theory¶
The problem (before any jargon)¶
Team script:
Log missing → grep fails → without set -e script continues → “check done” anyway → false green status.
What is a shell script? (simple words)¶
Analogy: A recipe card for the shell — step 1, step 2, same every time. Bash is the cook reading the card.
First line shebang picks the interpreter:
Interview line: “I start ops scripts with set -euo pipefail so failures stop the script and unset variables error.”
Safe mode — set -euo pipefail¶
| Flag | Meaning |
|---|---|
-e | Exit on first command failure |
-u | Error on unset variables |
-o pipefail | Pipeline fails if any command fails |
Arguments and exit codes¶
$? holds last exit code. Cron and systemd use it for success/failure.
Common pitfalls¶
- No shebang → wrong shell on cron
- Unquoted variables breaking on spaces
- Parsing
lsoutput (usefindor globs) - Missing
chmod +x
Hands-on Lab¶
Objective¶
Build host-check.sh with safe modes, arguments, intentional break, fix, and evidence under ~/rebash-linux/lab-shell.
Prerequisites¶
| Item | Notes |
|---|---|
| Ubuntu VM | bash |
| Lab only | Script checks local disk |
Lab environment¶
Real-world scenario¶
Mentor: “Give me a script I can run from cron that checks root disk usage and exits non-zero if above a threshold — must not hide errors.”
Step-by-step tasks¶
Task 1 – host-check.sh (working version)¶
Create host-check.sh:
#!/usr/bin/env bash
set -euo pipefail
THRESH="${1:-90}"
LOG="${HOME}/rebash-linux/lab-shell/host-check.log"
MOUNT="/"
usage="$(df -P "$MOUNT" | awk 'NR==2 {print $5}' | tr -d '%')"
{
echo "=== $(date -Is) ==="
echo "mount=$MOUNT usage_percent=$usage threshold=$THRESH"
} >> "$LOG"
if [[ "$usage" -ge "$THRESH" ]]; then
echo "FAIL: disk usage ${usage}% >= ${THRESH}%" >&2
exit 2
fi
echo "OK: disk usage ${usage}%"
exit 0
cd ~/rebash-linux/lab-shell
chmod +x host-check.sh
./host-check.sh 90 | tee run-ok.txt
grep -q '^OK:' run-ok.txt
tail -3 host-check.log | tee log-ok-tail.txt
echo $? | tee exit-code-ok.txt
Expected output
OK: disk usage … printed; exit code 0; log appended.
Task 2 – Break (disable -e), observe silent failure¶
Create host-check-broken.sh:
#!/usr/bin/env bash
# intentionally missing set -e for lab break demo
THRESH="${1:-90}"
false
echo "This line should not run if -e were enabled"
exit 0
cd ~/rebash-linux/lab-shell
chmod +x host-check-broken.sh
./host-check-broken.sh; echo "exit=$?" | tee broken-exit.txt
grep -q 'exit=0' broken-exit.txt
echo "break: script reported success after false command" | tee break-notes.txt
Expected output
Broken script prints misleading success line; exit=0 despite false — demonstrates why -e matters.
Task 3 – Fix threshold test and prove non-zero exit¶
cd ~/rebash-linux/lab-shell
./host-check.sh 0; echo "exit=$?" | tee fail-threshold-exit.txt || true
grep -q 'exit=2' fail-threshold-exit.txt
./host-check.sh 90 | tee run-after-fix.txt
echo "lab-shell OK" | tee evidence.txt
Expected output
Threshold 0 forces FAIL exit code 2 (disk always >= 0%). Normal threshold returns OK again.
Validation steps¶
-
host-check.shuses shebang andset -euo pipefail - Broken script demonstrates silent failure without
-e - Exit codes 0 vs 2 verified
- Log file receives timestamped entries
Common errors and fixes¶
| Error | Cause | Fix |
|---|---|---|
Permission denied | Not executable | chmod +x script.sh |
bad interpreter | Windows CRLF line endings | dos2unix script.sh |
| Unbound variable | -u and missing arg | ${1:-default} |
| Pipeline wrong status | Missing pipefail | set -o pipefail |
Challenge exercise¶
Add a second check: fail if loadavg first field > 10 (use uptime or /proc/loadavg) — keep script under 40 lines.
Learning outcomes¶
- You wrote a production-shaped mini script
- You saw why safe modes matter
- You can discuss exit codes in interviews
Cleanup¶
Validation¶
- Evidence under
~/rebash-linux/lab-shell - Can explain
set -euo pipefailin one sentence each - Ready for environment variables tutorial next
Code Walkthrough¶
#!/usr/bin/env bash— portable shebang finding bash on PATH.${1:-90}— default threshold if no argument — avoids unset with-u.df -P+ awk — predictable parsing; avoid baredflocale surprises.exit 2— distinct code for disk threshold vs generic 1.- Broken script without
-e— intentional anti-pattern for learning.
Security Considerations¶
- Quote variables:
"$LOG"prevents word splitting/injection. - Do not run curl|bash; review scripts before cron as root.
- Restrict script write permissions — attackers replace your script.
- Avoid secrets in scripts; use env files with tight permissions.
- Validate arguments (
[[ "$THRESH" =~ ^[0-9]+$ ]]) before use.
Common Mistakes¶
❌ No set -e on ops scripts.
✅ Failures cascade silently — always use safe modes unless you handle each error.
❌ Unquoted $variables.
✅ Filenames with spaces break scripts; quoting is mandatory.
❌ Ignoring exit codes in cron.
✅ Cron only emails on failure if exit non-zero — return meaningful codes.