Subject: bin/7015: test(1) operator precedence inconsistent with POSIX
To: None <gnats-bugs@gnats.netbsd.org>
From: Andrew_L. Moore <alm@SlewSys.Org>
List: netbsd-bugs
Date: 02/18/1999 16:41:44
>Number: 7015
>Category: bin
>Synopsis: test(1) operator precedence inconsistent with POSIX
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: bin-bug-people (Utility Bug People)
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Thu Feb 18 17:20:01 1999
>Last-Modified:
>Originator: Andrew_L. Moore
>Organization:
SlewSys Research
>Release: (source from NetBSD 1.3-current)
>Environment:
>Description:
To clarify the behavior of test(1) in a manner consistent with POSIX, the
following rules are proposed:
===============================================
1) A missing expression evaluates to false (a la `test' with no arguments).
2) Precedence of operators in descending order:
( )
binary (except -a and -o)
unary (except !)
string-length
!
-a
-o
-----------------------------------------------
* The precedence of binary over unary operators means that expressions
such as:
test \( -f = foo \)
are interpreted as binary comparisons, not syntax errors, as in the current
implementation. This is consistent with POSIX: `test -f = foo' is
interpreted as a binary comparison. For binary file operators, this rule
is important, since prefixing a filename argument -- e.g., with `./' --
might not be feasible.
* The precedence of parentheses over binary operators means that expressions
such as:
test \( = \)
are interpreted as string-length tests, not binary comparisons as in the
current implementation. This is consistent with POSIX to the extent that
the behavior of parentheses is "implementation-dependent". It also
allows parentheses to be used for disambiguating certain expressions.
For instance, the expression:
test -f = -o -o
is interpreted differently under the current and proposed rules. To force
the current interpretation under the new rules, it could be written:
test \( -f = \) -o -o
>How-To-Repeat:
(See above)
>Fix:
---- Cut Here ----
--- test.c 1999/02/14 09:16:21 1.1
+++ test.c 1999/02/18 22:37:03
@@ -150,6 +150,7 @@
static int binop __P((void));
static int filstat __P((char *, enum token));
static enum token t_lex __P((char *));
+static int isoperand __P((void));
static int getn __P((const char *));
static int newerf __P((const char *, const char *));
static int olderf __P((const char *, const char *));
@@ -170,42 +171,11 @@
argv[argc] = NULL;
}
- /* Implement special cases from POSIX.2, section 4.62.4 */
- switch (argc) {
- case 1:
- return 1;
- case 2:
- return (*argv[1] == '\0');
- case 3:
- if (argv[1][0] == '!' && argv[1][1] == '\0') {
- return !(*argv[2] == '\0');
- }
- break;
- case 4:
- if (argv[1][0] != '!' || argv[1][1] != '\0') {
- if (t_lex(argv[2]),
- t_wp_op && t_wp_op->op_type == BINOP) {
- t_wp = &argv[1];
- return (binop() == 0);
- }
- }
- break;
- case 5:
- if (argv[1][0] == '!' && argv[1][1] == '\0') {
- if (t_lex(argv[3]),
- t_wp_op && t_wp_op->op_type == BINOP) {
- t_wp = &argv[2];
- return !(binop() == 0);
- }
- }
- break;
- }
-
t_wp = &argv[1];
res = !oexpr(t_lex(*t_wp));
if (*t_wp != NULL && *++t_wp != NULL)
- syntax(*t_wp, "unknown operand");
+ syntax(*t_wp, "unexpected operator");
return res;
}
@@ -260,12 +230,15 @@
primary(n)
enum token n;
{
+ enum token nn;
int res;
if (n == EOI)
- syntax(NULL, "argument expected");
+ return 0; /* missing expression */
if (n == LPAREN) {
- res = oexpr(t_lex(*++t_wp));
+ if ((nn = t_lex(*++t_wp)) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(nn);
if (t_lex(*++t_wp) != RPAREN)
syntax(NULL, "closing paren expected");
return res;
@@ -401,6 +374,9 @@
}
while (op->op_text) {
if (strcmp(s, op->op_text) == 0) {
+ if (op->op_type == UNOP && isoperand() ||
+ op->op_num == LPAREN && *(t_wp+1) == 0)
+ break;
t_wp_op = op;
return op->op_num;
}
@@ -408,6 +384,26 @@
}
t_wp_op = (struct t_op *)0;
return OPERAND;
+}
+
+static int
+isoperand()
+{
+ struct t_op const *op = ops;
+ char *s;
+ char *t;
+
+ if ((s = *(t_wp+1)) == 0)
+ return 1;
+ if ((t = *(t_wp+2)) == 0)
+ return 0;
+ while (op->op_text) {
+ if (strcmp(s, op->op_text) == 0)
+ return op->op_type == BINOP &&
+ (t[0] != ')' || t[1] != '\0');
+ op++;
+ }
+ return 0;
}
/* atoi with error detection */
>Audit-Trail:
>Unformatted: