16

I'm perplexed but still guess I misunderstood Bash somehow.

/$ if [   -e /bin/grep ]; then echo yea; else echo nay ; fi
yea
/$ if [ ! -e /bin/grep ]; then echo yea; else echo nay ; fi
nay
/$ if [   -a /bin/grep ]; then echo yea; else echo nay ; fi
yea
/$ if [ ! -a /bin/grep ]; then echo yea; else echo nay ; fi
yea

Why negation ! reverses effect of -e test but not -a test?

Man bash says:

test:

3 arguments

The following conditions are applied in the order listed.

  1. If the second argument is one of the binary conditional operators listed above under CONDITIONAL EXPRESSIONS, the result of the expression is the result of the binary test using the first and third arguments as operands. The -a and -o operators are considered binary operators when there are three arguments.
  2. If the first argument is !, the value is the negation of the two-argument test using the second and third arguments.

Bash Conditional Expressions

Conditional expressions are used by the [[ compound command and the test and [ builtin commands

-a file
True if file exists.
-b file
True if file exists and is a block special file.
-c file
True if file exists and is a character special file.
-d file
True if file exists and is a directory.
-e file
True if file exists.

6
  • 1
    bash if -a vs -e option
    – Inian
    Nov 8 '21 at 13:57
  • 1
    @Inian, thank you. google have not shown that QA in top results, I searched for "bash negation and -a test results" Nov 8 '21 at 14:38
  • 2
    It isn't really relevant here, but these words are conventionally spelled "yea" and "nay." Nov 9 '21 at 2:54
  • 1
    @David, it kind of relevant. I was busy tackling the problem and skipped spell-checking. Feel free to edit ;-) Nov 9 '21 at 4:28
  • 1
    related: stackoverflow.com/questions/60499503/…
    – Z4-tier
    Nov 9 '21 at 5:50
28

-a is both a unary (for accessible, added for compatibility with the Korn shell, but otherwise non-standard and now redundant with -e) and binary (for and, in POSIX (with XSI) but deprecated there) operator.

Here [ ! -a /bin/grep ] invokes the binary operator as required by POSIX. It's [ "$a" -a "$b" ] to test whether $a is non-empty and $b is non empty, here with $a == ! and $b == /bin/grep. As both strings are non-empty, it returns true.

See also the "The -a and -o operators are considered binary operators when there are three arguments" in the text you quoted.

-a is deprecated in both the unary and binary form, the unary one because it's superseded by -e, the binary one because it makes for unreliable and ambiguous test expressions.

To test for file existence (though in effect, it's more a test whether the file is accessible, whether stat() would succeed on the path¹), use [ -e filepath ]. To and two conditions, use && between two invocations of [.

To test whether a string is non empty, I personally prefer the [ -n "$string" ] form over the [ "$string" ] one.

So:

  • test for file existence:

    [ -e "$file" ]   # not [ -a "$file" ]
    [ ! -e "$file" ] # not [ ! -a "$file" ]
    
  • test for two strings being non-empty:

    [ -n "$a" ] && [ -n "$b" ] # not [ "$a" -a "$b" ]
    [ "$a" ] && [ "$b" ]
    

From the rationale in the POSIX specification for the test (aka [) utility:

The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. (Many expressions using them are ambiguously defined by the grammar depending on the specific expressions being evaluated.) Scripts using these expressions should be converted to the forms given below. Even though many implementations will continue to support these obsolescent forms, scripts should be extremely careful when dealing with user-supplied input that could be confused with these and other primaries and operators.

and:

An early proposal used the KornShell -a primary (with the same meaning), but this was changed to -e because there were concerns about the high probability of humans confusing the -a primary with the -a binary operator.

The manuals of yash, bosh, GNU coreutils do guard against using binary -a/-o in their respective [/test implementations and zsh's manual never documented them², but many others including bash (the GNU shell) unfortunately still don't discourage their usage nor deprecate them.


¹ more on that in this answer of mine to a related stackoverflow Q&A

² a test/[ builtin was only added to zsh in version 2.0.3 in 1991. The [[ ... ]] special construct, from the Korn shell was always preferred there, and has its own syntax where && and || are used for and and or operators.

2
  • thank you. Is there an efficient way to update bash man pages in mainstream distros to add deprecated word to -a to avoid confusion of people like myself? Nov 8 '21 at 14:42
  • 5
    @Martian2020, that would definitely be a good idea. I would suggest you raise it upstreams at bug-bash@gnu.org Nov 8 '21 at 14:48
13

Bash parses [ ! -a WORD ] with -a being the binary operator meaning “and”, surrounded by two single-word tests which are true if non-empty. Thus [ ! -a "" ] is false and [ ! -a WORD ] is true if WORD is non-empty.

This parsing conforms to the POSIX specification (although strictly speaking it wouldn't have to since unary -a is not specified by POSIX). Incidentally, mksh and zsh parse this case in the same way, but ksh93 parses it with unary ! followed by unary -a.

To avoid the ambiguity, don't use the deprecated -a operator, use the standard -e operator, which doesn't have an identically-spelled binary operator. Also, to avoid ambiguity or errors in longer corner cases, use && and || as binary operators (either outside [ … ] or inside [[ … ]]) instead of the potentially ambiguous -a/-o inside [ … ].

2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.