When you notice you are typing the same sequence of commands for the third time, it is time to write a script. A Bash script is nothing more than a text file containing the commands you would type by hand, plus a few constructs for making decisions and repeating operations. You do not need to be a programmer: a few rules are enough, and above all a few prudent habits.
The first script
#!/usr/bin/env bash
echo "Free space on the main disk:"
df -h /
The first line, called the shebang, tells the system which interpreter to use. Once the file is saved as space.sh, you make it executable with chmod +x space.sh and run it with ./space.sh. The ./ is needed because, for safety, the current folder is not among the paths where the shell looks for commands.
Variables and quoting
folder="/home/anna/Documents"
date=$(date +%Y-%m-%d)
echo "Backup of $folder on $date"
Two rules prevent many errors. First: no spaces around the equals sign; folder = "..." is read as a command called folder. Second: always put variables in double quotes when you use them. If a variable contains Holiday photos, without quotes rm $file becomes rm Holiday photos and tries to delete two separate files. With rm "$file" the name stays whole.
The $(command) syntax puts a command's output into a variable. Single quotes, on the other hand, prevent any substitution: echo '$date' prints $date literally.
Script arguments
Arguments passed on the command line are available as $1, $2 and so on; $# gives their number and "$@" holds them all, each correctly separated. A check at the start makes the script friendlier:
if [ "$#" -ne 1 ]; then
echo "Usage: $0 folder" >&2
exit 1
fi
Conditions with if
The structure is if ... then ... else ... fi. The most common tests:
| Test | True if |
|---|---|
[ -f "$x" ] |
$x is an existing file |
[ -d "$x" ] |
$x is an existing directory |
[ -z "$x" ] |
the string is empty |
[ "$a" = "$b" ] |
the strings are equal |
[ "$n" -gt 10 ] |
the number is greater than 10 |
For numeric comparisons use -eq, -ne, -lt, -le, -gt, -ge, not the < and > symbols, which inside square brackets would be read as redirections. Bash also offers double brackets [[ ... ]], which are more forgiving with empty variables.
for and while loops
The for loop repeats a block for each item in a list:
for photo in *.jpg; do
echo "Processing $photo"
cp -- "$photo" "archive/$photo"
done
The double dash -- tells the command that the following arguments are not options: it protects against files whose name starts with a hyphen.
The while loop repeats as long as a condition is true. Its most typical use is reading a file line by line:
while IFS= read -r line; do
echo "Server: $line"
done < servers.txt
IFS= and -r stop leading spaces and backslashes from being altered: it is the form to use almost always.
Exit codes
Every command ends with a number: 0 means success, any other value an error. The $? variable holds the last command's code. Your script should also exit with exit 0 when all goes well and with a different value when something fails: that way it can be used in chains with && or in a scheduler that reports errors.
The three safety options
Many scripts start with this line after the shebang:
set -euo pipefail
-estops the script at the first failing command, instead of carrying on in an inconsistent state;-utreats the use of an undefined variable as an error, so a typo does not produce an empty path;-o pipefailmakes a pipe fail if any of its commands fails, not just the last.
The value of -u is obvious with an example: if you mistakenly write rm -rf "$FOLDR/" instead of $FOLDER, without -u the variable is empty and the command becomes rm -rf "/". With -u the script stops first.
Functions and readability
Functions group repeated operations:
log() {
echo "$(date '+%H:%M:%S') $*"
}
log "Starting copy"
Comments with #, clear variable names and short functions keep the script understandable even months later. Before putting it into use, the ShellCheck tool analyses the code and flags missing quotes, badly written tests and other common mistakes: it is one of the best ways to learn.
When to switch to another language
Bash is ideal for orchestrating commands and handling files. When a script grows beyond a few hundred lines, needs complex data structures or non-trivial calculations, it is better to move to a language such as Python, which offers better error handling and more suitable libraries.
In short
- A script starts with the shebang, is made executable with
chmod +xand run with./. - No spaces around
=, and variables always in double quotes. - Use
-eq,-gtand the like for numbers;while IFS= read -rto read files line by line. set -euo pipefailstops the script on errors and undefined variables.- Check scripts with ShellCheck and move to Python when the logic gets complicated.