The test(1) program is part of the POSIX specification. It provides a set of conditionals for comparing simple values and examining file attributes. Almost every non-trivial shell script uses test(1) extensively, as there is no other effective means of organising program logic solely within a command-line interpreter.
The test(1) specification is both messy and ambiguous. It also constrains comparisons to only three types (integers, strings, and files), with strange and often unpredicatable behaviour when given flonums. It is needlessly verbose (being constrained by infix notation) and is often counterintuitive in its choice of symbol semantics.
We scrapped all that. unix-test implements a superset of the standard (as well as a superset of each platform's implementation) in friendly s-expr format with meaningful symbols. The old familiar symbols we all know and 'love' are, of course, supported for maximal backward compatibility (as ridiculous as that sounds).
As far as I am aware, all POSIX (and GNU and BSD) flags and symbols are supported, with the following exceptions:
1. The -a flag ONLY means 'and', not 'file exists'. |
2. The GNU -o option flag is entirely unsupported, as is it is patently ridiculous. -o denotes 'or' only. |
3. Most flags check all of their operands for sanity before actually performing the operation, which differs from most implementations (and possibly the specification). This helps optimise flow for unlimited argument lists, however, and should rarely, if ever, give differing results. (The arguments are not actually evaluated; they are tested to ensure that all containing clauses could evaluate into the correct type(s).) |
4. All expressions are properly parenthesised and use proper (prefix) notation. |
1. All real numbers are supported by the comparison operators. |
2. Any flag for which it would make sense to allow unlimited operands will accept unlimited operands (see below for details). |
3. Sane flagnames and mnemonics are supported. |
4. Many, many more operations are supported. Reasonable feature-add requests will be granted. |
5. 'Internal clauses' (clauses that may only appear within other clauses, rather than at top-level) provide some extra functionality for common uses of test (such as string append, path resolution, file size, string length, etc.). |
6. It works on Win32. Certain types of operations will always fail, due to lack of support by the operating system, but it does work. |
There are two types of clauses that may be passed to unix-test :
1. Lists of the form (OPFLAG ARG...), where OPFLAG denotes the operation to perform, with ARG... as input(s).
2. Strings. This form is for backwards compatibility only.
All top-level clauses return a boolean value. String clauses return the value of (not (string-null? STR)). The return value of a list clause depends on its combination.
The extension of opflags to unlimited arguments requires implicit combiners in order to receive meaningful truth values. The order of arguments within a clause is always preserved. There are four distinct cases:
1. Flags that traditionally take in a single argument (such as file type flags) combine their values using and. This ensures immediate failure when any of the arguments does not satisfy the predicate. |
2. Flags that test equality (or other comparisons where all values may be checked against exactly one static value). This combines values merely by iterating down the list, comparing each following element against the first, and failing immediately if not satisfied. |
3. Flags that test an ordering or ranking of values (such as less-than) combine by comparing each ARG(N) with ARG(N+1). |
4. Flags that test INequality (or other comparisons where all values must be checked against all other values) combine via pair-fold to iterate down each sublist. |
At the moment, there is no way to change the combiners to use anything but and/fail-immediately. If other behaviours are desired, contact me.
Parses CLAUSE and performs all tests specified therein. Returns #t if the clause(s) were satisfied, and #f otherwise. See below for a table of all opflags. NOTE: Arguments to unix-test should NOT be quoted! The macro will quote it automatically.
Type | Description |
---|---|
CLAUSE |
any valid top-level clause list (or string). null is not a valid clause. CLAUSE is always processed by unix-test and returns a boolean. |
STRING |
string (including null string) or string-valued internal clause |
REAL |
integer or floating point number or number-valued internal clause |
BIT |
positive exact integer between 0 and C_WORD_SIZE - 1, inclusive, or integer-valued internal clause |
INT |
exact integer or integer-valued internal clause |
FILE |
string (including null string) naming a file or string-valued internal clause |
FD |
positive exact integer corresponding to an open file descriptor. |
The below table is a key for the table of operations.
Heading | Description |
---|---|
Op |
general syntax for the given operation |
Category |
loose classification of operation domain |
Test |
equivalent test(1) flag (or N/A) |
Flags |
list of all equivalent opflags |
Min |
minimum number of args allowed |
Max |
maximum number of args (0 == no limit) |
Description |
description of the operation |
The table below only describes top-level list clauses. For internal clauses, see the Internal Clauses section, below.
Op | Category | Test | Flags | Min | Max | Description |
---|---|---|---|---|---|---|
(and CLAUSE...) |
combiner |
-a | and a -a | 1 | 0 |
each CLAUSE is evaluated in order. true only if all clauses succeed are true. returns immediately on false. |
(or CLAUSE...) |
combiner |
-o | or o -o | 1 | 0 |
each CLAUSE is evaluated in order. true if any clause is true. returns immediately on true. |
(xor CLAUSE...) |
combiner |
N/A | xor | 1 | 0 |
each CLAUSE is evaluated in order. true if an odd number of clauses are true. this is the only multi-clause combiner guaranteed to evaluate all of its arguments. |
(not CLAUSE) |
combiner |
! | not ! | 1 | 1 |
true when CLAUSE fails, and vice versa. |
(length=0 STRING...) |
string-predicate |
-z | length=0 z -z | 1 | 0 |
true when every STRING is the null string. |
(length>0 STRING...) |
string-predicate |
-n | length>0 n -n | 1 | 0 |
true when every STRING is not the null string. |
(string= STRING1 STRING...) |
string-comparator |
= | string= = == | 2 | 0 |
true when every STRING has identical contents. |
(string!= STRING1 STRING...) |
string-comparator |
!= | string!= != | 2 | 0 |
true when no STRING has identical contents to any other. |
(string< STRING1 STRING...) |
string-comparator |
< | string< < | 2 | 0 |
true when STRING1 ... STRINGN are lexically ordered, strictly increasing. |
(string<= STRING1 STRING...) |
string-comparator |
<= | string<= <= | 2 | 0 |
true when STRING1 ... STRINGN are lexically ordered, strictly nondecreasing. |
(string>= STRING1 STRING...) |
string-comparator |
>= | string>= >= | 2 | 0 |
true when STRING1 ... STRINGN are lexically ordered, strictly nonincreasing. |
(string> STRING1 STRING...) |
string-comparator |
> | string> > | 2 | 0 |
true when STRING1 ... STRINGN are lexically ordered, strictly decreasing. |
(num-bit-set BIT INT...) |
num-predicate |
N/A | num-bit-set num-bit bit-set | 2 | 0 |
true when every INT has BIT set. |
(num= REAL1 REAL...) |
num-comparator |
-eq | num= eq -eq | 2 | 0 |
true when every REAL has identical value. |
(num!= REAL1 REAL...) |
num-comparator |
-ne | num!= ne -ne | 2 | 0 |
true when no REAL has identical value to any other. |
(num< REAL1 REAL...) |
num-comparator |
-lt | num< lt -lt | 2 | 0 |
true when REAL1 ... REALN are in strictly increasing order. |
(num<= REAL1 REAL...) |
num-comparator |
-le | num<= le -le | 2 | 0 |
true when REAL1 ... REALN are in strictly nondecreasing order. |
(num>= REAL1 REAL...) |
num-comparator |
-ge | num>= ge -ge | 2 | 0 |
true when REAL1 ... REALN are in strictly nonincreasing order. |
(num> REAL1 REAL...) |
num-comparator |
-gt | num> gt -gt | 2 | 0 |
true when REAL1 ... REALN are in strictly decreasing order. |
(exists FILE...) |
file-type-predicate |
-e | exists exist e -e | 1 | 0 |
true when every FILE is the name of an extant file. |
(blockdev FILE...) |
file-type-predicate |
-b | blockdev block-dev b -b | 1 | 0 |
true when every FILE exists and is a block device. |
(chardev FILE...) |
file-type-predicate |
-c | chardev char-dev c -c | 1 | 0 |
true when every FILE exists and is a character device. |
(directory FILE...) |
file-type-predicate |
-d | directory dir d -d | 1 | 0 |
true when every FILE exists and is a directory. |
(regular FILE...) |
file-type-predicate |
-f | regular regfile regular-file reg-file reg f -f | 1 | 0 |
true when every FILE exists and is a regular file. |
(symlink FILE...) |
file-type-predicate |
-h / -L | symlink sym-link h -h L -L | 1 | 0 |
true when every FILE exists and is a symbolic link. note that this is the only test predicate that does not dereference symlinks. |
(pipe FILE...) |
file-type-predicate |
-p | pipe fifo p -p | 1 | 0 |
true when every FILE exists and is a named pipe (FIFO). |
(socket FILE...) |
file-type-predicate |
-S | socket S -S | 1 | 0 |
true when every FILE exists and is a socket. |
(setuid FILE...) |
file-type-predicate |
-u | setuid set-uid u -u | 1 | 0 |
true when every FILE exists and has the setuid bit set. |
(setgid FILE...) |
file-type-predicate |
-g | setgid set-gid g -g | 1 | 0 |
true when every FILE exists and has the setgid bit set. |
(stickybit FILE...) |
file-type-predicate |
-k | stickybit sticky-bit sticky k -k | 1 | 0 |
true when every FILE exists and has the sticky bit set. |
(readable FILE...) |
file-type-predicate |
-r | readable read-perm r -r | 1 | 0 |
true when every FILE exists and is readable by the current user. |
(writable FILE...) |
file-type-predicate |
-w | writable writeable write-perm w -w | 1 | 0 |
true when every FILE exists and is writable by the current user. |
(executable FILE...) |
file-type-predicate |
-x | executable searchable exec-perm search-perm x -x | 1 | 0 |
true when every FILE exists and is executable/searchable by the current user. |
(size=0 FILE...) |
file-predicate |
N/A | size=0 | 1 | 0 |
true when every FILE exists and is empty (zero bytes). |
(size>0 FILE...) |
file-predicate |
-s | size>0 s -s | 1 | 0 |
true when every FILE exists and is not empty. |
(userowned FILE...) |
file-predicate |
-O | userowned user-owned user-is-owner owned-by-user O -O | 1 | 0 |
true when every FILE exists and the current effective uid is equivalent to the owner uid. |
(groupowned FILE...) |
file-predicate |
-G | groupowned group-owned group-is-owner owned-by-group G -G | 1 | 0 |
true when every FILE exists and the current effective gid is equivalent to the owner gid. |
(modified FILE...) |
file-predicate |
-N | modified N -N | 1 | 0 |
true when every FILE exists and has an access time identical to or older than its modification time. |
(equal FILE1 FILE...) |
file-comparator |
-ef | equal equals same ef -ef | 2 | 0 |
true when every FILE is identical (has the same device and inode numbers). |
(newer FILE1 FILE...) |
file-comparator |
-nt | newer nt -nt | 2 | 0 |
true when the modification times for each FILE are in strictly increasing order(where time value is measured from some zero in the past). |
(older FILE1 FILE...) |
file-comparator |
-ot | older ot -ot | 2 | 0 |
true when the modification times for each FILE are in strictly decreasing order (measured as above). |
(terminal FD...) |
misc-predicate |
-t | terminal term term-port t -t | 1 | 0 |
true when every FD is an open file descriptor on a terminal. |
The table below is a key for the internal clauses operations table.
Heading | Description |
---|---|
Op |
general syntax for the given operation |
Return Type |
type returned (determines where valid |
Test |
equivalent test(1) flag (or N/A) |
Flags |
list of all equivalent opflags |
Min |
minimum number of args allowed |
Max |
maximum number of args (0 == no limit) |
Description |
description of the operation |
Note especially the return values. Any clause with a return type of FILE or STRING may occur within any clause expecting either of these types. Likewise, INT return types may be used within any clause expecting any type of number. (The exception to this are clauses expecting file descriptors. Internal clauses cannot be used to generate FD types, at least at the moment.)
Op | Return Type | Test | Flags | Min | Max | Description |
---|---|---|---|---|---|---|
(size FILE) | INT | N/A | size | 1 | 1 |
returns the size of FILE in bytes if it exists and is stat'able, or -1 on error. |
(length STRING) | INT | -l | length l -l | 1 | 1 |
returns the length of STRING in bytes. |
(path FILE) | STRING | N/A | path | 1 | 1 |
returns the canonical absolute path (on the local platform) of FILE. note that this does not check for existence; it merely resolves partial or relative paths. |
(append STRING...) | STRING | N/A | append | 1 | 0 |
returns a new string comprised of each STRING appended in order. |
(!or INT...) | INT | N/A | !or | 1 | 0 |
returns the bitwise-or of all INTs. |
(!and INT...) | INT | N/A | !and | 1 | 0 |
returns the bitwise-and of all INTs. |
(!xor INT...) | INT | N/A | !xor | 1 | 0 |
returns the bitwise-xor of all INTs. |
(!1c INT) | INT | N/A | !1c | 1 | 1 |
returns the ones-complement representation of INT. |
(!2c INT) | INT | N/A | !2c | 1 | 1 |
returns the twos-complement representation of INT. |
Note that internal clauses may be used as arguments to internal clauses, provided they are of the appropriate return type.
None known
Copyright (C) 2008, Lenny Frank. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.