standards/133369: test(1) with 3 or 4 arguments
Jilles Tjoelker
jilles at stack.nl
Sat Apr 4 07:00:04 PDT 2009
>Number: 133369
>Category: standards
>Synopsis: test(1) with 3 or 4 arguments
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: freebsd-standards
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Sat Apr 04 14:00:03 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator: Jilles Tjoelker
>Release: FreeBSD 7.2-PRERELEASE i386
>Organization:
MCGV Stack
>Environment:
>Description:
test(1) does not follow the rules in POSIX exactly if there are 3 or 4
arguments, different from what the man page says.
In particular, different from what POSIX prescribes, test "$a" = "$b"
does not work properly for all values of $a and $b.
/bin/test and the test builtin in /bin/sh use the same code.
>How-To-Repeat:
An example, more tests are included in the patch.
Input:
/bin/test \( = \); echo $?
Expected result:
1
Actual result:
0
According to POSIX, the rule about the second argument being a binary
operator has priority above the rule about the first argument being '('
and the third argument being ')'.
>Fix:
Apply this patch. It includes additional test cases in TEST.sh.
--- test-posixfixes.patch begins here ---
--- src/bin/test/test.c.orig 2005-01-10 09:39:26.000000000 +0100
+++ src/bin/test/test.c 2009-04-02 02:28:28.000000000 +0200
@@ -163,6 +163,7 @@
struct t_op const *t_wp_op;
int nargc;
char **t_wp;
+int parenlevel;
static int aexpr(enum token);
static int binop(void);
@@ -171,7 +172,9 @@
static int getn(const char *);
static intmax_t getq(const char *);
static int intcmp(const char *, const char *);
-static int isoperand(void);
+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);
@@ -205,7 +208,14 @@
#endif
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");
@@ -268,12 +278,16 @@
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) {
@@ -410,8 +424,10 @@
}
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;
@@ -423,7 +439,7 @@
}
static int
-isoperand(void)
+isunopoperand(void)
{
struct t_op const *op = ops;
char *s;
@@ -431,19 +447,53 @@
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)
--- src/bin/test/TEST.sh.orig 2005-01-10 09:39:26.000000000 +0100
+++ src/bin/test/TEST.sh 2009-04-03 22:53:28.000000000 +0200
@@ -133,5 +133,35 @@
t 1 '""'
t 0 '! ""'
+t 0 '!'
+t 0 '\('
+t 0 '\)'
+
+t 1 '\( = \)'
+t 0 '\( != \)'
+t 0 '\( ! \)'
+t 0 '\( \( \)'
+t 0 '\( \) \)'
+t 0 '! = !'
+t 1 '! != !'
+t 1 '-n = \)'
+t 0 '! != \)'
+t 1 '! = a'
+t 0 '! != -n'
+t 0 '! -c /etc/passwd'
+
+t 0 '! \( = \)'
+t 1 '! \( != \)'
+t 1 '! = = ='
+t 0 '! = = \)'
+t 0 '! "" -o ""'
+t 1 '! "x" -o ""'
+t 1 '! "" -o "x"'
+t 1 '! "x" -o "x"'
+t 0 '\( -f /etc/passwd \)'
+t 1 '\( ! = \)'
+t 0 '\( ! "" \)'
+t 1 '\( ! -e \)'
+
echo ""
echo "Syntax errors: $ERROR Failed: $FAILED"
--- test-posixfixes.patch ends here ---
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-standards
mailing list