On Wed, Mar 27, 2024 at 10:00:06AM +0100, Phi Debian wrote: > $ man bash > ... > CONDITIONAL EXPRESSIONS > ... > > -a file > True if file exists. > -e file > True if file exists. > ... > > 'May be' would be nice for newbies to precise which options are [ specific > vs [[ specific for instance > > -a file > True if file exists ([[ only, for [ see test builtin) > > This to avoid things like > > $ [ -a /tmp ] && echo ok || echo nok > ok > $ [ ! -a /tmp ] && echo ok || echo nok > ok > > I know it is obvious, unless this is intended to force a complete > multi-pass man read...
I wouldn't say it's "obvious" what's happening here. The problem is that there are two different "-a" operators, one unary, and one binary. "help test" documents both of them: hobbit:~$ help test | grep -- -a -a FILE True if file exists. EXPR1 -a EXPR2 True if both expr1 AND expr2 are true. In your first example, you are using the unary -a operator on a file: > $ [ -a /tmp ] && echo ok || echo nok > ok This one returns true because there are two arguments, and the first one is not '!', and is a "unary primary" (POSIX wording). Therefore it uses the unary -a and tests for existence of the second argument as a file. In your second example, you are using the binary -a operator: > $ [ ! -a /tmp ] && echo ok || echo nok > ok Here, you have three arguments, and argument 2 is a "binary primary" (POSIX wording again), so it's treated as if you had written this: [ ! ] && [ /tmp ] && echo ok || echo nok This is simply performing two string length tests. Both strings are non-empty (the first is one character, and the second is four), so the result is true. The check for whether the first argument is '!' is not performed, because the "$2 is a binary primary" check comes first. This is how POSIX documents it. So... how do you work around this? Well, the easiest way would be to stop using -a entirely. Both the unary *and* binary forms. The unary form can be replaced by -e, and then everything works as you expect. The binary form should be discarded along with "-o", and never used. You are much better off stringing together multiple test or [ commands instead: if [ -e "$logdir" ] && [ -e "$outputdir" ]; then ... This removes all ambiguity, and is in fact the only supported way to write this under POSIX restrictions (">4 arguments: The results are unspecified.")