Lesson 01 of 8 · Modules
Scripts, variables & quoting
Write your first real scripts: the shebang, running them, variables, the three kinds of quoting, command substitution and exit codes, plus the quoting rule that prevents most Bash bugs.
Why Bash still matters
Every Linux server has Bash. Installers, container entrypoints, CI steps, cron jobs and "just automate this" tasks are very often Bash scripts. Writing them safely is a core skill, even if you later move bigger tools to Python.
A script is a recipe card for the computer. Instead of telling it each step by hand every morning, you write the steps down once, and the computer follows the card whenever you ask, in exactly the same way every time.
Your first script
#!/usr/bin/env bash
# hello.sh: my first script
name="Asha"
echo "Hello, $name. Today is $(date +%A)."
$ chmod +x hello.sh
$ ./hello.sh
Hello, Asha. Today is Saturday.
#!/usr/bin/env bash(the shebang) says which interpreter runs the file.chmod +xmakes it executable;./runs it from the current directory.bash -x hello.shprints each command before running it. It's the easiest debugger there is.
Variables
name="Asha" # no spaces around =
count=3
greeting="Hi $name" # expands to: Hi Asha
echo "$greeting, you have $count messages"
echo "${name}_backup" # braces separate the name from following text → Asha_backup
Environment variables ($HOME, $PATH, $USER) are just variables your script inherits. export VAR=value passes a variable on to programs your script starts.
The one rule: quote your variables
Bash splits unquoted variables on spaces and expands wildcards in them. That causes most Bash bugs:
$ file="my report.txt"
$ ls $file
ls: cannot access 'my': No such file or directory
ls: cannot access 'report.txt': No such file or directory
$ ls "$file"
my report.txt
| Quoting | Variables expand? | Word splitting / globbing? | Use for |
|---|---|---|---|
"double" |
✅ | ❌ | Almost everything |
'single' |
❌ | ❌ | Literal text, regexes, JSON, awk programs |
| none | ✅ | ✅ (dangerous) | Rarely, deliberately |
Quote by default
Write "$var", "$@" and "$(command)" unless you have a specific reason not to. shellcheck (lesson 07) flags every place you forgot.
Capturing output: command substitution
today=$(date +%F) # 2026-09-27
kernel=$(uname -r)
files=$(ls /etc | wc -l)
echo "On $today, kernel $kernel, /etc has $files entries"
Use $(...), not the old backticks. It nests cleanly and is easier to read.
Exit status: did it work?
Every command returns an exit status: 0 = success, anything else = failure.
$ grep -q root /etc/passwd; echo $?
0
$ grep -q nobody-here /etc/passwd; echo $?
1
$ ls /nope; echo $?
ls: cannot access '/nope': No such file or directory
2
Chain commands on success or failure:
mkdir -p /tmp/work && cd /tmp/work # cd only if mkdir succeeded
ping -c1 -W1 10.0.0.1 || echo "gateway unreachable"
Your script's own exit status is the last command's, or whatever you pass to exit:
exit 0 # success
exit 1 # failure
Try it: a system summary script
Write summary.sh that prints:
- The hostname and today's date.
- The number of logged-in users (
who | wc -l). - Free space on
/(df -h / | tail -1). - Whether nginx is running: use
systemctl is-active --quiet nginxwith&&and||to print "nginx: up" or "nginx: down".
Run it with bash -x summary.sh to see each step. Then create a file named my report.txt and write a line that wc -ls it through a variable. Make it fail without quotes, then fix it.
Going deeper: small things that bite later
#!/usr/bin/env bashfinds bash onPATH(portable across systems).#!/bin/shmeans POSIX sh, which on Debian/Ubuntu is dash and lacks many Bash features.localvariables in functions (lesson 03) avoid clobbering globals.readonly CONFIG=/etc/app.confprotects constants.- Prefer
printf '%s\n' "$var"overechofor arbitrary data:echotreats some values (like-n) as options.
Recap
- Shebang +
chmod +x; debug withbash -x. name=valuewith no spaces; use as"$name"or"${name}".- Quote your variables; single quotes for literal text.
$(command)captures output;$?andexitcarry exit status (0 = success); chain with&&and||.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.