Classic Shell Scripting - Arnold Robbins [73]
... ]
[ else
statements-if-all-else-fails ]
fi
(The square brackets indicate optional parts; they are not typed literally.) The shell's syntax is based loosely on that of Algol 68, which is the language that Steven Bourne, the author of the V7 shell, really admired. It is most notable for the use of opening and closing keywords to bracket statement groups, instead of the begin and end delimiters of Algol 60 and Pascal, or the { and } popularized by C and used almost universally in other programmable Unix tools.
In the case at hand, you can probably guess how this works: the shell runs the first group of statements between the if and the then. If the last statement executed exits successfully, it runs statements-if-true-1. Otherwise, if an elif is present, it tries the next group of statements. If the last one exits successfully, it runs statements-if-true-2. It continues in this fashion until it encounters a command that exits successfully, running the corresponding group of statements.
If none of the if or elif statements is true, and an else clause is present, it executes statements-if-all-else-fails. Otherwise, it does nothing. The exit status of the entire if...fi statement is that of the last command executed following a then or else. If none was executed, the exit status is 0. For example:
if grep pattern myfile > /dev/null
then
... Pattern is there
else
... Pattern is not there
fi
grep exits with a 0 status if myfile contains pattern. It exits with a value of 1 if no line matched the pattern, and with a value greater than 1 if there was an error. Based on grep's exit status, the shell chooses which group of statements to execute.
Logical NOT, AND, and OR
Sometimes it's easier to phrase your tests in the negative: "if John is not at home, then ..." The way to do this in the shell is to precede a pipeline with an exclamation mark:
if ! grep pattern myfile > /dev/null
then
... Pattern is not there
fi
POSIX introduced this notation in the 1992 standard. You will likely see older shell scripts that use the colon (:) command, which does nothing, to handle this kind of case:
if grep pattern myfile > /dev/null
then
: # do nothing
else
... Pattern is not there
fi
Besides just testing the inverse of things with !, you will often want to test multiple subconditions, with AND and OR constructs. ("If John is home, and he isn't busy, then ..."). When the operator && separates two commands, the shell executes the first one. If it exits successfully, the shell executes the second one. If that exits successfully too, then the entire group is considered to have succeeded:
if grep pattern1 myfile && grep pattern2 myfile
then
... myfile contains both patterns
fi
In contrast, the || operator is used when you want to test if one condition or the other is true:
if grep pattern1 myfile || grep pattern2 myfile
then
... One or the other is present
fi
Both of these are short-circuit operators, meaning that the shell stops evaluating commands as soon as it can determine the truth-value of the entire group. For example, in command1 && command2, if command1 fails, then there is no way the whole thing could be true, so command2 never executes. Similarly for command1 || command2: if command1 succeeds, there is no reason to execute command2.
Don't try to be overly "terse" and use && and || to replace the if statement. We have little objection to something short and simple, like this:
$ who | grep tolstoy > /dev/null && echo tolstoy is logged on
tolstoy is logged on
This runs who | grep ..., and if that succeeds it prints the message. However, we have seen vendor shell scripts use constructs like this:
some_command && {
one command
a second command
and a third command
}
The braces serve to group all the commands together, which are executed only if some_command succeeds. This would be much more cleanly written using a simple if:
if some_command
then
one command
a second command
and a third command
fi
The test Command
The aptly named test command exists to perform