Hands-on guide to writing and running shell scripts (bash). You can try everything in WSL/VirtualBox.
1) What’s a shell script?
A text file with commands you could type in the terminal—saved so you can run them again and again.
Typical first line (shebang) tells the OS which shell to use:
#!/usr/bin/env bash
(Portable and recommended.)
2) Your first script (hello)
1.
Create a file hello.sh
:
#!/usr/bin/env bash
echo "Hello, $USER! Today is $(date '+%A, %d %b %Y')."
2. Make it executable and run:
chmod +x hello.sh
./hello.sh
# or: bash hello.sh # runs with bash even if not executable
If you see ^M
or
“bad interpreter”: file has Windows line endings. Fix with:
sudo apt install dos2unix
dos2unix hello.sh
3) Script structure you’ll reuse
#!/usr/bin/env bash
set -Eeuo pipefail
# -e: exit on error -u: error on unset vars
# -o pipefail: catch errors in pipelines
# -E: keep ERR traps working in functions
# ------------- config / defaults -------------
LOG_DIR="${HOME}/logs"
mkdir -p "$LOG_DIR"
# ------------- functions ---------------------
log() { printf '[%(%F %T)T] %s\n' -1 "$*"; }
# ------------- main logic --------------------
log "Starting job…"
# your commands here
log "All done."
4) Variables & quoting (super important)
name="Aisha"
echo "$name" # good: quoted (safe with spaces)
echo $name # risky: word splitting
n=$((5+7)) # arithmetic → 12
PATH="$HOME/bin:$PATH" # prepend to PATH
·
Always
quote "${var}"
in scripts.
·
Use $(command)
for command substitution (not backticks).
5) Arguments & exit status
·
Positional args: $0
(script name), $1
, $2
…; $#
count; $@
all args (preserve
spaces when quoted).
·
Last command’s
exit code: $?
(0 = success).
#!/usr/bin/env bash
set -Eeuo pipefail
name="${1:-Student}" # default if $1 missing
echo "Hello, $name"
echo "Previous exit code was: $?"
exit 0
6) Conditions, tests, and case
file="notes.txt"
if [[ -f "$file" ]]; then
echo "File exists"
elif [[ -d "$file" ]]; then
echo "It's a directory"
else
echo "Not found"
fi
ext="png"
case "$ext" in
jpg|jpeg) echo "JPEG image" ;;
png) echo "PNG image" ;;
*) echo "Unknown" ;;
esac
Common test flags: -f
file, -d
dir, -e
exists, -s
non-empty, -x
executable, -r
readable, -w
writable, -nt
newer than, -ot
older
than.
7) Loops & arrays
# for loop
for f in *.txt; do
echo "Found: $f"
done
# while loop (read lines)
while IFS= read -r line; do
echo ">$line"
done < file.txt
# arrays (bash)
arr=(alpha beta gamma)
echo "${arr[1]}" # beta
for x in "${arr[@]}"; do echo "$x"; done
8) Functions & return codes
greet() {
local who="${1:-World}"
echo "Hello, $who"
}
greet "Linux"
·
return
sets function exit code (0/1/…); echo
prints output.
·
Use local
for function-local variables.
9) Input/Output, pipes, here-docs
echo "Log line" >> app.log # append
wc -l < data.txt # read from file
grep -i "error" app.log | tee errs.txt # see + save
# here-doc (write multi-line text)
cat > config.ini <<'EOF'
[app]
env=prod
EOF
10) Parsing options with getopts
(idiomatic)
#!/usr/bin/env bash
set -Eeuo pipefail
usage(){ echo "Usage: $0 [-n name] [-v] file"; }
name="Student"; verbose=0
while getopts ":n:v" opt; do
case "$opt" in
n) name="$OPTARG" ;;
v) verbose=1 ;;
\?) usage; exit 2 ;;
esac
done
shift $((OPTIND-1)) # move past parsed options
file="${1:-}"
[[ -z "$file" ]] && { usage; exit 2; }
(( verbose )) && echo "Name: $name | File: $file"
echo "Hello, $name. File has $(wc -l < "$file") lines."
Run:
./tool.sh -n Aisha -v notes.txt
11) Traps & cleanup (robust scripts)
Clean temporary files even on Ctrl+C or errors:
tmp="$(mktemp -d)"
cleanup(){ rm -rf "$tmp"; }
trap cleanup EXIT INT TERM
# …use "$tmp"…
12) Running scripts (5 common ways)
./run.sh # needs +x and executable shebang
bash run.sh # run with bash explicitly
bash -x run.sh # debug: show commands as they run
ENV=prod ./run.sh # set an env var for this run
PATH="$HOME/bin:$PATH" ./run.sh
Where to place scripts
·
Personal: ~/bin
(add to PATH in ~/.bashrc
)
·
System-wide: /usr/local/bin
(needs sudo)
13) Cron: run on a schedule (intro)
crontab -e
# Every day at 01:30
30 1 * * * /home/you/bin/backup.sh >> /home/you/logs/backup.log 2>&1
Cron has a minimal environment—use absolute paths and export what you need inside the script.
14) Debugging checklist
·
bash
-x script.sh
(trace), or add set -x
temporarily.
·
Print variables: declare -p var arr
.
·
Check exit codes:
cmd || echo "failed:
$?"
.
·
Validate shell: #!/usr/bin/env bash
and run with bash
, not
/bin/sh
.
·
Static analysis: sudo apt install shellcheck &&
shellcheck script.sh
.
15) Three mini projects (copy-paste ready)
A) Safer backup (with timestamp, logging)
#!/usr/bin/env bash
set -Eeuo pipefail
SRC="${1:-$HOME/Documents}"
DST="${2:-$HOME/backups/$(date +%F_%H-%M-%S)}"
mkdir -p "$DST"
rsync -a --delete "$SRC"/ "$DST"/
echo "Backup completed to $DST"
Run: ./backup.sh ~/lab ~/backup_dir
B) Log scanner (summaries)
#!/usr/bin/env bash
set -Eeuo pipefail
log="${1:?usage: $0 LOGFILE}"
errs=$(grep -c -i 'error' "$log" || true)
warns=$(grep -c -i 'warn' "$log" || true)
echo "Errors: $errs Warnings: $warns"
C) Tiny CLI with subcommands
#!/usr/bin/env bash
set -Eeuo pipefail
cmd="${1:-help}"; shift || true
case "$cmd" in
greet) echo "Hello, ${1:-Student}";;
sum) awk '{s+=$1} END{print s}' "${1:-/dev/stdin}";;
help|*) echo "Usage: $0 {greet [name]|sum file}";;
esac
16) Safety rules (student edition)
·
Always quote variables: "$var"
.
·
Prefer set -Eeuo pipefail
.
·
Use --
to stop option parsing when forwarding args: cmd -- "$file"
.
·
Avoid sudo
in scripts unless required; document why.
· Don’t rely on current directory; use absolute paths or:
·
cd "$(dirname "$0")" # run relative to script’s location
Exam-ready bullets
·
Shebang selects interpreter; make script executable with chmod +x
.
·
Use set -euo pipefail
for robustness; quote variables.
·
Arguments: $1..$9
, $#
, $@
; parse options with getopts
.
·
Tests: [[ … ]]
with -f/-d/-e
, etc.; loops: for/while
; functions with local
.
·
Exit
status: 0
success; check with $?
.
·
Traps clean up temp files; use mktemp
.
·
Debug with bash -x
, lint with shellcheck
.
· Cron needs absolute paths and a minimal environment.
If you want, I can turn these into a step-by-step lab sheet (with checkboxes) or a print-ready cheat sheet for your class.