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
| Test | True when |
|---|---|
-e F / -f F / -d F | F exists / is a regular file / is a directory |
-r F / -w F / -x F | F is readable / writable / executable |
-s F / -L F | F is not empty / is a symbolic link |
-z S / -n S | string S is empty / not empty |
S1 = S2 / S1 != S2 | strings equal / different |
N1 -eq N2, -ne, -lt, -le, -gt, -ge | integer comparisons |
! EXPR | EXPR 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
| Command | What it does |
|---|---|
test -f FILE | Exit 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 …; fi | The conditional block. |
[ ! -e FILE ] | Negate a test. |
Quiz
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`
Which line is correct?
- `if [-f notes.txt]; then`
- `if [ -f notes.txt ]; then`
- `if ( -f notes.txt ) then`
How do you test that the number in `$n` is at least 10?
- `[ "$n" >= 10 ]`
- `[ "$n" -ge 10 ]`
- `[ "$n" = 10+ ]`
`name=""`. What happens with `[ $name = root ]`?
- It is false, as expected
- A syntax error: the empty variable vanishes and `[` sees `= root ]`
- It is true
Which keyword closes an `if` block?
- `end`
- `}`
- `fi`
Practice
Write an executable script `/root/lab/l84/check.sh` that takes a path as `$1` and prints `exists` if it exists, or `missing` otherwise.
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.
`/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.