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