Bash Scripting for Engineers›02 · Conditions & loops

Lesson 02 of 8 · Modules

Conditions & loops

Make scripts decide and repeat: if with [[ ]], file and string tests, case, for and while loops, reading files line by line safely, arithmetic, and arrays.

Beginner
Key wordsif[[ ]]testcaseforwhile readarithmeticarrays

Deciding with if

#!/usr/bin/env bash
config=/etc/myapp/config.yaml

if [[ -f "$config" ]]; then
  echo "Using $config"
elif [[ -d /etc/myapp ]]; then
  echo "Directory exists but no config: creating a default"
else
  echo "No /etc/myapp at all" >&2      # >&2 sends the message to stderr
  exit 1
fi

if is a fork in the road: if it's raining, take an umbrella, otherwise wear sunglasses. A loop is doing the same chore for every item on a list: for each plate on the table, wash it.

if runs a command and checks its exit status. [[ … ]] is a command built for tests, but any command works:

if grep -q "^deploy:" /etc/passwd; then
  echo "user deploy exists"
fi
if systemctl is-active --quiet nginx; then
  echo "nginx is up"
fi

Common tests

Test True when
-f path / -d path / -e path regular file / directory / anything exists
-r / -w / -x path readable / writable / executable
-z "$s" / -n "$s" string empty / not empty
"$a" == "$b" / != strings equal / differ
$s == web-* matches a glob pattern (inside [[ ]])
$s =~ ^[0-9]+$ matches a regular expression
(( n > 10 )) arithmetic comparison

Combine with &&, || and !: [[ -f "$f" && -r "$f" ]].

case: many choices

case "$1" in
  start)   systemctl start myapp ;;
  stop)    systemctl stop myapp ;;
  restart) systemctl restart myapp ;;
  *)       echo "usage: $0 {start|stop|restart}" >&2; exit 2 ;;
esac

for loops

for host in web-01 web-02 web-03; do
  echo "== $host"
  ssh "$host" uptime
done

for f in /var/log/nginx/*.log; do
  [[ -e "$f" ]] || continue            # skip if the glob matched nothing
  echo "$f: $(wc -l < "$f") lines"
done

for i in {1..5}; do echo "attempt $i"; done

Reading files line by line

while IFS= read -r host; do
  [[ -z "$host" || "$host" == \#* ]] && continue    # skip blanks and comments
  echo "checking $host"
done < hosts.txt

Don't loop over $(cat file)

for line in $(cat hosts.txt) splits on every space and expands *. Use while IFS= read -r for lines.

Arithmetic

count=0
count=$((count + 1))
(( count++ ))
if (( count >= 3 )); then echo "three or more"; fi
echo "$(( 17 / 5 )) remainder $(( 17 % 5 ))"     # 3 remainder 2 (integers only)

Arrays

servers=(web-01 web-02 db-01)
echo "first: ${servers[0]}, count: ${#servers[@]}"
servers+=(cache-01)
for s in "${servers[@]}"; do echo "$s"; done

Always loop with "${array[@]}" (quoted) so elements containing spaces stay whole.

Try it: a service checker

Create services.txt with one service per line (ssh, cron, nginx, a comment line and a blank line). Write check.sh that:

  1. Reads the file with while IFS= read -r, skipping blanks and comments.
  2. For each service prints OK or DOWN using systemctl is-active --quiet.
  3. Counts the DOWN services and exits with status 1 if any were down (0 otherwise).
  4. Accepts an optional argument --quiet (use case) that prints only the DOWN ones.

Going deeper: loop subtleties

  • A while read loop at the end of a pipe runs in a subshell, so variables set inside are lost after it: cmd | while read … → prefer while read …; done < <(cmd) (process substitution).
  • [[ ]] is Bash; [ ] is POSIX test, with more quoting pitfalls. Use [[ ]] in Bash scripts.
  • shopt -s nullglob makes non-matching globs expand to nothing instead of the literal pattern.
  • For parallel loops over many hosts, xargs -P or GNU parallel beat backgrounding with & by hand.

Recap

  • if checks a command's exit status; [[ … ]] provides file, string and pattern tests; (( … )) does arithmetic.
  • case for multiple choices; for over words, globs and ranges.
  • Read lines with while IFS= read -r line; do …; done < file.
  • Arrays: arr=(…), "${arr[@]}", ${#arr[@]}.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.