+.\"-
.\" Copyright (c) 1991, 1993
.\" The Regents of the University of California. All rights reserved.
.\"
.\" SUCH DAMAGE.
.\"
.\" @(#)test.1 8.1 (Berkeley) 5/31/93
-.\" $FreeBSD: src/bin/test/test.1,v 1.11.2.5 2001/12/14 14:22:09 ru Exp $
-.\" $DragonFly: src/bin/test/test.1,v 1.7 2007/10/20 17:56:46 swildner Exp $
+.\" $FreeBSD: src/bin/test/test.1,v 1.31 2010/09/11 10:49:56 brucec Exp $
.\"
-.Dd May 31, 1993
+.Dd September 10, 2010
.Dt TEST 1
.Os
.Sh NAME
.Ar string
is not the null
string.
-.It Ar \&s\&1 Cm \&= Ar \&s\&2
+.It Ar s1 Cm = Ar s2
True if the strings
-.Ar \&s\&1
+.Ar s1
and
-.Ar \&s\&2
+.Ar s2
are identical.
-.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+.It Ar s1 Cm != Ar s2
True if the strings
-.Ar \&s\&1
+.Ar s1
and
-.Ar \&s\&2
+.Ar s2
are not identical.
-.It Ar \&s\&1 Cm \&< Ar \&s\&2
+.It Ar s1 Cm < Ar s2
True if string
-.Ar \&s\&1
+.Ar s1
comes before
-.Ar \&s\&2
-based on the ASCII value of their characters.
-.It Ar \&s\&1 Cm \&> Ar \&s\&2
+.Ar s2
+based on the binary value of their characters.
+.It Ar s1 Cm > Ar s2
True if string
-.Ar \&s\&1
+.Ar s1
comes after
-.Ar \&s\&2
-based on the ASCII value of their characters.
-.It Ar \&s\&1
-True if
-.Ar \&s\&1
-is not the null
-string.
-.It Ar \&n\&1 Fl \&eq Ar \&n\&2
+.Ar s2
+based on the binary value of their characters.
+.It Ar n1 Fl eq Ar n2
True if the integers
-.Ar \&n\&1
+.Ar n1
and
-.Ar \&n\&2
+.Ar n2
are algebraically
equal.
-.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+.It Ar n1 Fl ne Ar n2
True if the integers
-.Ar \&n\&1
+.Ar n1
and
-.Ar \&n\&2
+.Ar n2
are not
algebraically equal.
-.It Ar \&n\&1 Fl \> Ar \&n\&2
+.It Ar n1 Fl gt Ar n2
True if the integer
-.Ar \&n\&1
+.Ar n1
is algebraically
greater than the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl ge Ar n2
True if the integer
-.Ar \&n\&1
+.Ar n1
is algebraically
greater than or equal to the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \< Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl lt Ar n2
True if the integer
-.Ar \&n\&1
+.Ar n1
is algebraically less
than the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \&le Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl le Ar n2
True if the integer
-.Ar \&n\&1
+.Ar n1
is algebraically less
than or equal to the integer
-.Ar \&n\&2 .
+.Ar n2 .
.El
.Pp
+If
+.Ar file
+is a symbolic link,
+.Nm
+will fully dereference it and then evaluate the expression
+against the file referenced, except for the
+.Fl h
+and
+.Fl L
+primaries.
+.Pp
These primaries can be combined with the following operators:
.Bl -tag -width Ar
.It Cm \&! Ar expression
or
.Ar expression2
are true.
-.It Cm \&( Ns Ar expression Ns Cm \&)
+.It Cm \&( Ar expression Cm \&)
True if expression is true.
.El
.Pp
.Sh GRAMMAR AMBIGUITY
The
.Nm
-grammar is inherently ambiguous. In order to assure a degree of consistency,
+grammar is inherently ambiguous.
+In order to assure a degree of consistency,
the cases described in the
.St -p1003.2 ,
section D11.2/4.62.4, standard
are evaluated consistently according to the rules specified in the
-standards document. All other cases are subject to the ambiguity in the
+standards document.
+All other cases are subject to the ambiguity in the
command semantics.
-.Sh DIAGNOSTICS
+.Pp
+In particular, only expressions containing
+.Fl a ,
+.Fl o ,
+.Cm \&(
+or
+.Cm \&)
+can be ambiguous.
+.Sh EXIT STATUS
The
.Nm
utility exits with one of the following values:
-.Bl -tag -width Ds
+.Bl -tag -width indent
.It 0
expression evaluated to true.
.It 1
.Sh SEE ALSO
.Xr builtin 1 ,
.Xr expr 1 ,
-.Xr sh 1
+.Xr sh 1 ,
+.Xr symlink 7
.Sh STANDARDS
The
.Nm
utility implements a superset of the
.St -p1003.2
specification.
+.Sh BUGS
+Both sides are always evaluated in
+.Fl a
+and
+.Fl o .
+For instance, the writable status of
+.Pa file
+will be tested by the following command even though the former expression
+indicated false, which results in a gratuitous access to the file system:
+.Dl "[ -z abc -a -w file ]"
+To avoid this, write
+.Dl "[ -z abc ] && [ -w file ]"
/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */
-/*
+/*-
* test(1); version 7-like -- author Erik Baalbergen
* modified by Eric Gisin to be used as built-in.
* modified by Arnold Robbins to add SVR3 compatibility
*
* This program is in the Public Domain.
*
- * $FreeBSD: src/bin/test/test.c,v 1.29.2.7 2002/09/10 09:10:57 maxim Exp $
- * $DragonFly: src/bin/test/test.c,v 1.7 2004/11/07 19:42:16 eirikn Exp $
+ * $FreeBSD: src/bin/test/test.c,v 1.55 2010/03/28 13:16:08 ed Exp $
*/
#include <sys/types.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
+#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include "bltin/bltin.h"
#include "error.h"
#else
-static void error(const char *, ...) __dead2 __printflike(1, 2);
+#include <locale.h>
+
+static void error(const char *, ...) __dead2 __printf0like(1, 2);
static void
error(const char *msg, ...)
struct t_op const *t_wp_op;
int nargc;
char **t_wp;
-
-static int aexpr (enum token);
-static int binop (void);
-static int equalf (const char *, const char *);
-static int filstat (char *, enum token);
-static int getn (const char *);
-static long long getll (const char *);
-static int intcmp (const char *, const char *);
-static int isoperand (void);
-static int newerf (const char *, const char *);
-static int nexpr (enum token);
-static int oexpr (enum token);
-static int olderf (const char *, const char *);
-static int primary (enum token);
-static void syntax (const char *, const char *);
-static enum token t_lex (char *);
+int parenlevel;
+
+static int aexpr(enum token);
+static int binop(void);
+static int equalf(const char *, const char *);
+static int filstat(char *, enum token);
+static int getn(const char *);
+static intmax_t getq(const char *);
+static int intcmp(const char *, const char *);
+static int isunopoperand(void);
+static int islparenoperand(void);
+static int isrparenoperand(void);
+static int newerf(const char *, const char *);
+static int nexpr(enum token);
+static int oexpr(enum token);
+static int olderf(const char *, const char *);
+static int primary(enum token);
+static void syntax(const char *, const char *);
+static enum token t_lex(char *);
int
main(int argc, char **argv)
if (--argc <= 0)
return 1;
+#ifndef SHELL
+ setlocale(LC_CTYPE, "");
+#endif
/* XXX work around the absence of an eaccess(2) syscall */
egid = getegid();
euid = geteuid();
nargc = argc;
t_wp = &argv[1];
- res = !oexpr(t_lex(*t_wp));
+ parenlevel = 0;
+ if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
+ /* Things like ! "" -o x do not fit in the normal grammar. */
+ --nargc;
+ ++t_wp;
+ res = oexpr(t_lex(*t_wp));
+ } else
+ res = !oexpr(t_lex(*t_wp));
if (--nargc > 0)
syntax(*t_wp, "unexpected operator");
if (n == EOI)
return 0; /* missing expression */
if (n == LPAREN) {
+ parenlevel++;
if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
- RPAREN)
+ RPAREN) {
+ parenlevel--;
return 0; /* missing expression */
+ }
res = oexpr(nn);
if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
syntax(NULL, "closing paren expected");
+ parenlevel--;
return res;
}
if (t_wp_op && t_wp_op->op_type == UNOP) {
}
while (op->op_text) {
if (strcmp(s, op->op_text) == 0) {
- if ((op->op_type == UNOP && isoperand()) ||
- (op->op_num == LPAREN && nargc == 1))
+ if (((op->op_type == UNOP || op->op_type == BUNOP)
+ && isunopoperand()) ||
+ (op->op_num == LPAREN && islparenoperand()) ||
+ (op->op_num == RPAREN && isrparenoperand()))
break;
t_wp_op = op;
return op->op_num;
}
static int
-isoperand(void)
+isunopoperand(void)
{
struct t_op const *op = ops;
char *s;
if (nargc == 1)
return 1;
- if (nargc == 2)
- return 0;
s = *(t_wp + 1);
+ if (nargc == 2)
+ return parenlevel == 1 && strcmp(s, ")") == 0;
t = *(t_wp + 2);
while (op->op_text) {
if (strcmp(s, op->op_text) == 0)
return op->op_type == BINOP &&
- (t[0] != ')' || t[1] != '\0');
+ (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
op++;
}
return 0;
}
+static int
+islparenoperand(void)
+{
+ struct t_op const *op = ops;
+ char *s;
+
+ if (nargc == 1)
+ return 1;
+ s = *(t_wp + 1);
+ if (nargc == 2)
+ return parenlevel == 1 && strcmp(s, ")") == 0;
+ if (nargc != 3)
+ return 0;
+ while (op->op_text) {
+ if (strcmp(s, op->op_text) == 0)
+ return op->op_type == BINOP;
+ op++;
+ }
+ return 0;
+}
+
+static int
+isrparenoperand(void)
+{
+ char *s;
+
+ if (nargc == 1)
+ return 0;
+ s = *(t_wp + 1);
+ if (nargc == 2)
+ return parenlevel == 1 && strcmp(s, ")") == 0;
+ return 0;
+}
+
/* atoi with error detection */
static int
getn(const char *s)
errno = 0;
r = strtol(s, &p, 10);
+ if (s == p)
+ error("%s: bad number", s);
+
if (errno != 0)
error((errno == EINVAL) ? "%s: bad number" :
"%s: out of range", s);
}
/* atoi with error detection and 64 bit range */
-static long long
-getll(const char *s)
+static intmax_t
+getq(const char *s)
{
char *p;
- long long r;
+ intmax_t r;
errno = 0;
- r = strtoll(s, &p, 10);
+ r = strtoimax(s, &p, 10);
+
+ if (s == p)
+ error("%s: bad number", s);
if (errno != 0)
error((errno == EINVAL) ? "%s: bad number" :
static int
intcmp (const char *s1, const char *s2)
{
- long long q1, q2;
+ intmax_t q1, q2;
- q1 = getll(s1);
- q2 = getll(s2);
+ q1 = getq(s1);
+ q2 = getq(s2);
if (q1 > q2)
return 1;
if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
return 0;
- if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec)
+ if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
return 1;
- if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec)
+ if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
return 0;
- return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec);
+ return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
}
static int