<aside> 📜
Module 10 — Bash Scripting
A script is a file of shell commands the machine runs for you — the step from typing commands to automating them. Everything you have learned since Module 1 becomes reusable, repeatable, and safe to run at 3 a.m. unattended. This module teaches scripts that behave under pressure: they fail loudly instead of silently, they handle bad input, and they clean up after themselves. Sloppy scripts are how outages happen; disciplined ones are the DevOps craft.
Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
</aside>
<aside> 🚦
Before you start
You need Modules Module 1 — What Linux Is–Module 9 — Package Management — this module composes the whole track. Load-bearing: exit codes and quoting (Module 5), chmod +x and ./ (Module 4), grep/sed/awk (Modules 6–7), and processes/signals (Module 8, for trap). Tools: an Ubuntu 24.04 machine; we install shellcheck (a script linter) in Part D. Sandbox: mkdir ~/scripting && cd ~/scripting.
</aside>
<aside> ℹ️
This page uses Mermaid diagram blocks. Notion shows them as code by default — click the block and set it to Preview to see the diagram. This reminder appears once per page.
</aside>
<aside> 📖
Official docs: bash(1) · Bash Reference Manual
</aside>
🧠 A script is just a text file of commands. Two things make it runnable as a program. First, the shebang — the very first line, #!/usr/bin/env bash — which tells the kernel which interpreter to feed the file to when you execute it. (The #! is a literal magic marker the kernel looks for; env bash finds bash via PATH rather than hard-coding its location, which is why it's more portable than #!/bin/bash.) Second, the execute bit (Module 4): chmod +x script.sh, so you can run it as ./script.sh. Without the shebang, the kernel doesn't know it's a bash script; without +x, you can't run it by path (though bash script.sh always works — that names the interpreter explicitly, so it needs neither).
<aside> 🧠
Counter-intuitive: the shebang is a comment to bash and an instruction to the kernel. That #! line starts with #, which bash treats as a comment and ignores — yet it is the most important line in the file, because the kernel reads it first (before bash is even involved) to decide which interpreter to launch. Two different readers, two different meanings, same line. And it only matters when you run ./script; running bash script ignores the shebang entirely, which is why a script with a wrong shebang can "work" one way and fail the other.
</aside>
<aside> 🧩
Real-world analogy — the language label on a document
A document sent for translation carries a cover note: "translate from French". The mailroom (kernel) reads that note to route it to the French translator; the translator themselves ignores the note and starts on the body. The shebang is that cover note — routing instruction for the dispatcher, invisible to the worker.
Where the analogy stops working. A mislabelled document reaches the wrong translator, who quickly notices the text isn't French and complains. A wrong shebang hands your bash script to sh (a stricter, smaller shell), which may run most of it fine and then fail cryptically on the one bash-only feature — a silent, partial success that is far more confusing than an outright rejection. The dispatcher's mistake surfaces deep inside the work, not at the door.
</aside>
🧪 Exercise A1.1 — Your first real script
cd ~/scripting
cat > hello.sh <<'EOF'
#!/usr/bin/env bash
echo "Hello from a script"
echo "I am running as: $(whoami)"
EOF
chmod +x hello.sh
./hello.sh # run by path (needs shebang + execute bit)
bash hello.sh # run by naming the interpreter (needs neither)
<aside> 📖
Official docs: bash(1) — Positional Parameters
</aside>
🧠 A script that only does one fixed thing is barely better than a command. Positional parameters make it reusable: inside a script, $1 is the first argument you passed, $2 the second, and so on; $@ is all arguments, $# is how many, and $0 is the script's own name. So ./deploy.sh web-01 v2 gives the script $1=web-01, $2=v2. The same quoting law from Module 5 applies with full force: always "$1", always "$@" — unquoted, an argument containing spaces splits apart. ("$@" specifically expands to each argument as a separate quoted word — the one correct way to forward arguments onward.)
<aside> 🧩
Real-world analogy — the fill-in-the-blank work order
A script with positional parameters is a work-order template: "Deploy version to host ". The blanks ($1, $2) are filled when the order is issued, so one template serves every deployment. Without blanks you'd need a separate form per host — which is exactly what hard-coded scripts are.
Where the analogy stops working. A human filling a form asks when a blank is left empty. A script does not: an unsupplied $1 expands to the empty string, silently, and the script marches on with a hole where the hostname should be (Module 5's form-letter trap, now inside your automation). Guarding against missing arguments is your job, not bash's — B-part conditionals and the ${1:?} guard exist precisely for this.
</aside>
🧪 Exercise A2.1 — One script, many inputs
cd ~/scripting
cat > args.sh <<'EOF'
#!/usr/bin/env bash
echo "Script name: $0"
echo "First argument: $1"
echo "All arguments: $@"
echo "Number of arguments: $#"
EOF
chmod +x args.sh
./args.sh alpha beta gamma
./args.sh # run with NO arguments — watch the empty blanks