Shell scripts: automate what you repeat
Lesson 10 of 11
10Shell scripts: automate what you repeat
The big pictureThe right kind of lazy engineer writes a script once and saves hours. Backups, project setup, cleaning files: all ten-line scripts.
What the picture shows: From scattered commands to a file you run with one command and that tells you whether it worked.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
A script is a written kitchen recipe: instead of remembering "heat, then add, then stir" each time, you read the sheet. Variables are blanks in the recipe ("amount of rice: ___") you fill each time for the number of guests.
A bash script is a text file with a shebang naming the interpreter, plus execute permission. Positional arguments $1..$9, $@ and $#, and expansions like ${var:-default}. [[ ]] for tests, and $? for the last exit code. set -e stops at the first failure, -u forbids undefined variables, and pipefail catches failures inside pipes. Use shellcheck to catch the usual mistakes.
Before You Start
🔎Everyday Linux commands
You know find, grep and tar.
🔐Permissions
You know chmod.
The Core Idea
1Your first script
The first line tells the system which program reads the file. Then your commands, as you would type them. Give it execute permission and run it with ./.
#!/usr/bin/env bash set -euo pipefail echo "Today is $(date +%F)" echo "You are in $PWD"
chmod +x hello.sh ./hello.sh
2Variables and arguments
name="Sara" with no spaces around =. Read it as "$name". What you type after the script name arrives as $1, $2.
rm $file. If the name has a space, "my notes.txt", it tries to delete two files, my and notes.txt. Always write "$file".3Conditions and loops
if [[ -d "$dir" ]] asks "is this a folder?", and for f in *.txt goes through every text file.
#!/usr/bin/env bash
set -euo pipefail
dir="${1:-.}" # first argument, or the current folder
if [[ ! -d "$dir" ]]; then
echo "not a folder: $dir" >&2
exit 1
fi
count=0
for f in "$dir"/*.txt; do
[[ -e "$f" ]] || continue
lines=$(wc -l < "$f")
echo "$f: $lines lines"
count=$((count + 1))
done
echo "$count text files"4Exit codes: did it work?
Every command ends with a number: 0 for success, anything else for failure. $? gives the last one, && runs the next only on success, and || only on failure. Your script should exit 1 on error so other tools know it failed.
Worked example
Problem: every day you pack the notes folder into a dated file and keep the last 7 copies. Solution:
#!/usr/bin/env bash
set -euo pipefail
src="${1:?usage: backup.sh <folder>}"
dest="$HOME/backups"
mkdir -p "$dest"
file="$dest/$(basename "$src")-$(date +%F).tar.gz"
tar -czf "$file" "$src"
echo "saved $file"
# keep the newest 7, delete the rest
ls -1t "$dest"/*.tar.gz | tail -n +8 | xargs -r rm --Myth vs Reality
| Myth | Reality |
|---|---|
| Scripts are only for sysadmins. | Every developer writes scripts to set up, test and deploy projects. |
| Python is always better than bash. | For gluing shell commands together bash is shorter. When logic and maths grow, move to Python. |
Test your understanding
What does set -e at the top of a script do?
Without it the script carries on after a failure and may delete or write in the wrong place.
You ran ./run.sh photos 3. What is $2?
$1 is the first argument after the name, $2 the second.
Which exit code means success?
In the shell zero is success and anything else is some kind of failure.
Quick review cards
#!/usr/bin/env bash naming the interpreter.Check Your Understanding
Why write "$file" in quotes?
So the name stays one word even with spaces or symbols, instead of splitting into several arguments.
How do you give an argument a default?
dir="${1:-.}" takes $1, or . if none was passed.
When do you move from bash to Python?
When logic, maths and data structures pile up, or the script grows past a page.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Done with this lesson?
Sign in to save your progress.