Hello,

test -t X

Always returns false and doesn't report an error about that
invalid number (beside the point here, but in ksh/zsh, that X is treated
as an arithmetic expression and evaluates to 0 if $X is not set).

While:

test -t X -a Y

returns a "too many arguments" error.

test X -a -t -a Y

returns false (without error and regardless of whether any fd is a tty)
while

test X -a Y -a -t

returns true

While for other unary operators that gives:

$ bash -c 'test X -a -x -a Y'
bash: line 1: test: too many arguments

No big deal as in all those the behaviour is unspecfied by
POSIX (non-numeric argument to -t, or more than 4 arguments, -a
deprecated).

It seems to be explained by what looks like a remnant from the
time where [ -t ] was short for [ -t 1 ] in the code of
unary_operator() in test.c

>  /* the only tricky case is `-t', which may or may not take an argument. */

Not anymore.

>  if (op[1] == 't')
>    {
>      advance (0);
>      if (pos < argc)
>       {
>         if (legal_number (argv[pos], &r))
>           {
>             advance (0);
>             return (unary_test (op, argv[pos - 1], 0));
>           }
>         else
>           return (FALSE);

Maybe the intention was to do a isatty(1) here instead of always
returning false, but that and the fact that advance() is not called only
confuses things.

>       }
>      else
>       return (unary_test (op, "1", 0));

That part is never reached AFAICT as unary_operator() is never
called with pos == argc.

I beleive that whole code can go as -t is now always a unary
operator, and it would be more useful to report an error when
the operand is not a number.

I also noticed that the fact that -a/-o were deprecated (by POSIX at
least) and made for unreliable test expressions was not noted in the
manual. So I suggest the patch below:

diff --git a/doc/bashref.texi b/doc/bashref.texi
index 85e729d5..00fbab69 100644
--- a/doc/bashref.texi
+++ b/doc/bashref.texi
@@ -4215,14 +4215,14 @@ Operator precedence is used when there are five or more 
arguments.
 @item ! @var{expr}
 True if @var{expr} is false.
 
-@item ( @var{expr} )
+@item ( @var{expr} ) (DEPRECATED)
 Returns the value of @var{expr}.
 This may be used to override the normal precedence of operators.
 
-@item @var{expr1} -a @var{expr2}
+@item @var{expr1} -a @var{expr2} (DEPRECATED)
 True if both @var{expr1} and @var{expr2} are true.
 
-@item @var{expr1} -o @var{expr2}
+@item @var{expr1} -o @var{expr2} (DEPRECATED)
 True if either @var{expr1} or @var{expr2} is true.
 @end table
 
@@ -4283,11 +4283,26 @@ Otherwise, the expression is parsed and evaluated 
according to
 precedence using the rules listed above.
 @end enumerate
 
-@item 5 or more arguments
+@item 5 or more arguments (DEPRECATED)
 The expression is parsed and evaluated according to precedence
 using the rules listed above.
 @end table
 
+In the 4 or 5 arguments case, the use of @samp{(}, @samp{)}, binary
+@samp{-a}, binary @samp{-o} make for unreliable test expressions. For
+instance @code{test "$x" -a ! "$y"}  becomes a test for whether a
+@samp{!} file exists if @code{$x} is @samp{(} and @code{$y} is
+@samp{)} and @code{[ -f "$file" -a ! -L "$file" ]} fails with a
+syntax error for a file called @samp{==}. Which explains why those
+are deprecated as they have been in the POSIX specification of the
+@code{test} utility since 2008.
+
+Each invocation of @code{[} / @code{test} should perform a single test
+and several invocations may be chained with the @code{&&} or @code{||}
+shell operators to achieve the same result as the @code{-a} and
+@code{-o} operators reliably as in @code{test "$x" && test ! "$y"} or
+@code{[ -f "$file" ] && [ ! -L "$file" ]} in the examples above.
+
 When used with @code{test} or @samp{[}, the @samp{<} and @samp{>}
 operators sort lexicographically using ASCII ordering.
 
diff --git a/test.c b/test.c
index 2b12197a..e16337a5 100644
--- a/test.c
+++ b/test.c
@@ -476,24 +476,6 @@ unary_operator (void)
   if (test_unop (op) == 0)
     return (FALSE);
 
-  /* the only tricky case is `-t', which may or may not take an argument. */
-  if (op[1] == 't')
-    {
-      advance (0);
-      if (pos < argc)
-       {
-         if (legal_number (argv[pos], &r))
-           {
-             advance (0);
-             return (unary_test (op, argv[pos - 1], 0));
-           }
-         else
-           return (FALSE);
-       }
-      else
-       return (unary_test (op, "1", 0));
-    }
-
   /* All of the unary operators take an argument, so we first call
      unary_advance (), which checks to make sure that there is an
      argument, and then advances pos right past it.  This means that
@@ -603,7 +585,7 @@ unary_test (char *op, char *arg, int flags)
 
     case 't':  /* File fd is a terminal? */
       if (legal_number (arg, &r) == 0)
-       return (FALSE);
+       integer_expected_error (arg);
       return ((r == (int)r) && isatty ((int)r));
 
     case 'n':                  /* True if arg has some length. */



Reply via email to