Classic Shell Scripting - Arnold Robbins [75]
* * *
Both ksh93 and bash support a number of additional tests. More information is available in Section 14.3.2.
The POSIX algorithm for test is summarized in Table 6-7.
Table 6-7. POSIX algorithm for test
Arguments
Argument values
Result
0
Exit false (1).
1
If $1 is non-null
Exit true (0).
If $1 is null
Exit false (1).
2
If $1 is !
Negate result of single-argument test, $2.
If $1 is a unary operator
Result of the operator's test.
Anything else
Unspecified.
3
If $2 is a binary operator
Result of the operator's test.
If $1 is !
Negate result of double-argument test, $2 $3.
If $1 is ( and $3 is )
Result of single-argument test, $2 (XSI).
Anything else
Unspecified.
4
If $1 is !
Negate result of three-argument test, $2 $3 $4.
If $1 is ( and $4 is )
Result of two-argument test, $2 $3 (XSI).
Anything else
Unspecified.
> 4
Unspecified.
For portability, the POSIX standard recommends the use of shell-level tests for multiple conditions, instead of the -a and -o operators. (We also recommend this.) For example:
if [ -f "$file" ] && ! [ -w "$file" ]
then
# $file exists and is a regular file, but is not writable
echo $0: $file is not writable, giving up. >&2
exit 1
fi
There are some Section 14.1 associated with test as well:
Arguments are required
For this reason, all shell variable expansions should be quoted so that test receives an argument, even if it turns out to be the null string. For example:
if [ -f "$file" ] ... Correct
if [ -f $file ] ... Incorrect
In the second case, should $file happen to be empty, test receives one less argument than it needs, leading to strange behavior.
String comparisons are tricky
In particular, if a string value is empty, or starts with a minus, test could become confused. This leads to the rather ugly, but widespread convention of prefixing string values with the letter X. (The use of X is arbitrary, but traditional.)
if [ "X$answer" = "Xyes" ] ...
You will see this used in many shell scripts, and it is in fact used in examples throughout the POSIX standard.
The algorithm just given for test, along with always quoting all arguments, should be enough for a modern version of test, even if the first argument starts with a minus. Thus we don't see a lot of need for the leading X prefix in new programs. However, if maximal portability is more important than readability, you may wish to use it (and we do so, occasionally).
test can be fooled
When checking the access of files mounted over a network, it is possible for unusual combinations of mount options and file permissions to "fool" test into thinking that a file is readable, when in fact the operating system won't let you access the file. Thus, although:
test -r a_file && cat a_file
should always work in principle, it can fail in practice.[2] About all you can do is add another layer of defensive programming:
if test -r a_file && cat a_file
then
# cat worked, proceed on
else
# attempt to recover, issue an error message, etc.
fi
Numeric tests are integer-only
You cannot do any kind of floating-point arithmetic with test. All numeric tests work only with integers. (ksh93 understands floating-point numbers, but you can't rely on that feature for full portability.)
Example 6-1 presents an improved version of the finduser script presented in Section 2.6. This version tests $#, the number of command-line arguments, and prints an error message if exactly one isn't supplied.