Read

Module 9 · Shell scripting basics

Conditions: test, [ ] and if

Make a script decide: the test command, its file, string and number operators, and the if/elif/else structure built on top of it.

What you will learn

  • Write `if`/`elif`/`else`/`fi` blocks.
  • Use `test` (`[ ]`) operators for files, strings and integers.
  • Understand that `if` tests an exit status, so any command can be a condition.

An if in the shell does not evaluate a boolean expression. It runs a command and looks at its exit status: 0 means success and takes the then branch; anything else goes to else. That is why if grep -q root /etc/passwd; then … works with no brackets at all. The command most often used as a condition is test, which exists to compare things and exit 0 or 1, and [ is simply another name for test that requires a closing ].

~% test -f /etc/passwd; echo $?
0
~% [ -f /nope ]; echo $?
1
~% [ 5 -gt 3 ] && echo "five is bigger"
five is bigger
~% [5 -gt 3 ]
sh: [5: not found

Because [ is a command, it needs spaces around it like any other word: [ -f file ], never [-f file]. Everything inside is an argument, so variables with spaces must be quoted, [ "$name" = "root" ], or an empty variable makes the command collapse into [ = root ] and fail with a syntax error.

The operators you will actually use

TestTrue when
-e F / -f F / -d FF exists / is a regular file / is a directory
-r F / -w F / -x FF is readable / writable / executable
-s F / -L FF is not empty / is a symbolic link
-z S / -n Sstring S is empty / not empty
S1 = S2 / S1 != S2strings equal / different
N1 -eq N2, -ne, -lt, -le, -gt, -geinteger comparisons
! EXPREXPR is false

Strings and numbers have different operators on purpose: [ 10 -gt 9 ] is true, but [ 10 \> 9 ] compares as text and is false, because "1" sorts before "9". Combine tests with the shell's own && and || between separate brackets, [ -f f ] && [ -r f ]; it reads better than -a/-o inside one pair.

if, elif, else, fi

~% cat lab/l84/check.sh
#!/bin/sh
target="$1"
if [ -z "$target" ]; then
    echo "usage: check.sh FILE" >&2
    exit 1
elif [ -d "$target" ]; then
    echo "$target is a directory with $(ls "$target" | wc -l) entries"
elif [ -f "$target" ]; then
    echo "$target is a file of $(wc -c < "$target") bytes"
else
    echo "$target does not exist"
fi
~% lab/l84/check.sh /etc
/etc is a directory with 24 entries
~% lab/l84/check.sh /etc/passwd
/etc/passwd is a file of 340 bytes
~% lab/l84/check.sh /nope
/nope does not exist

The structure is if condition; then … elif condition; then … else … fi. The ; before then is just a line separator; you may put then on its own line instead. fi closes the block (it is if backwards). Indentation is for you, the shell ignores it, but keep it consistent: nested blocks without indentation are unreadable within a week.

Any command can be the condition. if cmp -s a b; then echo same; fi compares two files; if ping -c 1 -W 1 10.0.2.2 >/dev/null 2>&1; then … checks the network. Redirecting to /dev/null keeps the output out of the way; the if only cares about the exit status.

Commands in this lesson

CommandWhat it does
test -f FILEExit 0 if FILE is a regular file.
[ -d DIR ]Same as test, bracket form.
[ "$a" = "$b" ]String equality.
[ "$n" -ge 10 ]Integer comparison.
if …; then …; elif …; then …; else …; fiThe conditional block.
[ ! -e FILE ]Negate a test.

Quiz

  1. What does `if` actually examine?

    • The exit status of the command after it
    • Whether the text between brackets is non-empty
    • The value of `$1`
  2. Which line is correct?

    • `if [-f notes.txt]; then`
    • `if [ -f notes.txt ]; then`
    • `if ( -f notes.txt ) then`
  3. How do you test that the number in `$n` is at least 10?

    • `[ "$n" >= 10 ]`
    • `[ "$n" -ge 10 ]`
    • `[ "$n" = 10+ ]`
  4. `name=""`. What happens with `[ $name = root ]`?

    • It is false, as expected
    • A syntax error: the empty variable vanishes and `[` sees `= root ]`
    • It is true
  5. Which keyword closes an `if` block?

    • `end`
    • `}`
    • `fi`

Practice

  1. Write an executable script `/root/lab/l84/check.sh` that takes a path as `$1` and prints `exists` if it exists, or `missing` otherwise.

  2. Write an executable script `/root/lab/l84/grade.sh` that takes a number as `$1` and prints `pass` if it is 50 or more, `fail` otherwise.

  3. `/root/lab/l84/thing` is either a file or a directory. With a single `if` line, write `dir` into `/root/lab/l84/kind.txt` if it is a directory, otherwise `file`.

Open this lesson in the app to do the tasks in a real Linux machine and have them checked.