Intermediate · Automation basics: scheduling, archives and shell scripts · topic 38 of 57 · 35 min
Bash scripts: conditions, loops and safe habits
Explanation
if and tests
#!/bin/bash
file="/etc/redhat-release"
if [ -f "$file" ]; then
echo "This is a RHEL-family system"
elif [ -d /etc/apt ]; then
echo "This looks like Debian or Ubuntu"
else
echo "Unknown system"
fi| Test | True when |
|---|---|
| -f FILE / -d DIR | a regular file / a directory exists |
| -e PATH | anything exists at that path |
| -r / -w / -x FILE | readable / writable / executable by you |
| -z "$s" / -n "$s" | string is empty / not empty |
| "$a" = "$b" / != | strings are equal / different |
| $a -eq $b, -ne, -lt, -le, -gt, -ge | numbers compare (equal, not equal, less than...) |
| ! test | the test is false |
[[ ]] is bash's improved test: it handles empty variables better, allows && and || inside, and supports pattern matching like [[ $host == web* ]]. In bash scripts [[ ]] is usually the safer choice; [ ] works in every POSIX shell. Any command can be the condition too: if grep -q asha /etc/passwd; then ... uses grep's exit code.
case: many choices
case "$1" in
start) systemctl start httpd ;;
stop) systemctl stop httpd ;;
status) systemctl status httpd --no-pager ;;
*) echo "Usage: $0 {start|stop|status}" >&2; exit 2 ;;
esacLoops
# for: over a list
for server in web01 web02 db01; do
ping -c1 -W1 "$server" > /dev/null && echo "$server up" || echo "$server DOWN"
done
# for: over numbers
for i in {1..5}; do
useradd "trainee$i"
done
# while read: over the lines of a file (one username per line)
while read -r user; do
id "$user" &> /dev/null || useradd "$user"
done < /root/new-users.txtAvoid for line in $(cat file) for files: it splits on every space, not on lines. Use while read -r.
Functions
log() {
echo "$(date '+%F %T') $*" >> /var/log/backup.log
}
log "Backup started"
tar -czf "/backup/www-$(date +%F).tar.gz" /var/www || { log "tar failed"; exit 1; }
log "Backup finished"Safe habits: set -euo pipefail
- set -e: stop the script when a command fails (instead of carrying on with half the work done).
- set -u: treat an unset variable as an error. This catches typos like $BACKUP_DRI that would otherwise be empty.
- set -o pipefail: a pipeline fails if ANY part fails, not just the last command.
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backup"
[ -d "$BACKUP_DIR" ] || { echo "No $BACKUP_DIR" >&2; exit 1; }
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +14 -print -deleteCommon mistakes
- Missing spaces in tests: if [$a -eq 1] fails with "command not found".
- Using = for numbers or -eq for text: [ "$a" -eq "yes" ] is an error. Use = for strings, -eq for numbers.
- Forgetting ;; at the end of each case branch, or fi / done / esac at the end of a block.
- Writing scripts that only work when run from one directory. Use absolute paths.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Bash scripts: conditions, loops and safe habits · 1/6
if [ -f /etc/redhat-release ]; then
echo "RHEL family"
fiif runs commands only when a test is true. Put spaces inside the brackets.
Prefer a video by a teacher? Find free videos on “bash if for while loop case functions set -euo pipefail tutorial” on YouTube ↗
In real life: A morning health check at a hospital IT team
The IT team of a hospital in Hyderabad runs a script every morning at 7:30 from a timer. It loops over a list of servers, checks that each answers ping and that its disk is below 85%, and writes one line per server to a log. If any check fails, the script exits with 1 and the timer's failure shows up in the monitoring dashboard before doctors start their rounds.
#!/bin/bash
set -euo pipefail
status=0
while read -r host; do
if ! ping -c1 -W2 "$host" &> /dev/null; then
echo "$host DOWN"; status=1; continue
fi
use=$(ssh "$host" df --output=pcent / | tail -1 | tr -dc '0-9')
if [ "$use" -ge 85 ]; then echo "$host disk $use%"; status=1; else echo "$host ok"; fi
done < /etc/healthcheck/hosts.txt
exit "$status"Practice (0/4 done)
Which test is true when /backup is a directory?
Which line has the bug? 1: count=5 2: if [$count -gt 3]; then 3: echo "many" 4: fi
A script reads wanted usernames from a file and creates the missing ones. Model it: write plan_users(existing, wanted_text) where existing is a set of usernames and wanted_text is the file's text (one name per line; skip blank lines and lines starting with #; strip spaces). Return the list of commands the script would run, in file order, without duplicates: "useradd NAME" for each missing user.
Write /usr/local/bin/backup-www.sh that: uses set -euo pipefail; takes an optional destination directory (default /backup); creates it if missing; makes /DEST/www-YYYY-MM-DD.tar.gz of /var/www; deletes archives older than 7 days there; logs each step with a log function; and exits 0 on success. Run it twice and check the log.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Archives and compression: tar, gzip, bzip2 and xzAutomation basics: scheduling, archives and shell scripts · same module
- One-time jobs with at, and systemd timersAutomation basics: scheduling, archives and shell scripts · same module
- Bash scripting, text tools and cronDevOps: Git, Docker, Kubernetes and CI/CD · Linux for DevOps
- Loops: repeating workFrontend Web Development · JavaScript basics
- while loopsPython Programming · Loops: repeating work
- Loops: for, while, do-while and range-forC++ Programming (modern C++ and DSA) · Decisions, loops and functions
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which loop reads a file line by line correctly?
Answer: while read -r l; do ... done < f
while read -r handles lines with spaces.
What does set -u do?
Answer: Treats unset variables as errors
It catches typos in variable names.
Which compares two NUMBERS for equality?
Answer: [ "$a" -eq "$b" ]
-eq is numeric; = is for strings.
What ends a case branch?
Answer: ;;
;; ends a branch; esac ends the whole case.
Why use set -o pipefail?
Answer: So a failure in any part of a pipeline makes the pipeline fail
Otherwise only the last command's status counts.
What does set -o pipefail change?Related
Answer: A pipeline fails if any command in it fails, not just the last one
Without pipefail, pg_dump failing in "pg_dump | gzip" would be hidden because gzip succeeded.
What does $(hostname) do inside a script?Related
Answer: Runs hostname and puts its output there
Command substitution.
From Bash scripts: variables, quoting, arguments and exit codes
Which loop is best to visit every item of an array?Related
Answer: for...of
for...of gives you each item in turn, with no index to manage.
When does Python check the condition of a while loop?Related
Answer: Before every round of the loop
The condition is checked before each round. When it becomes False, the loop stops.
From while loops
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Automation basics: scheduling, archives and shell scripts”. The module exam comes next.
- Module 7 exam: Automation basics · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Boot, recovery and containers. 57 topics left in the course before the final exam.
← Previous: Bash scripts: variables, quoting, arguments and exit codes