What is the difference of -a and -e in bash's conditional expressions?

From man bash:

CONDITIONAL EXPRESSIONS
[...]
       -a file
              True if file exists.
[...]
       -e file
              True if file exists.
  1. So what is the difference between [ -a $FILE ] and [ -e $FILE ], if any?
  2. If there is no real difference, why do two flags for the same purpose exist?
Asked By: polym

||

.1. So what is the difference between [ -a $FILE ] and [ -e $FILE ], if any?

There is no difference at all.

In line 505-507 in test.c of the bash version 4.2.45(1)-release:

case 'a':           /* file exists in the file system? */
case 'e':
  return (sh_stat (arg, &stat_buf) == 0);

That indicates that there is no real difference between both flags.

.2. If there is no real difference, why do two flags for the same purpose exist?

See cuonglm’s answer.

Answered By: polym

In bash, with context of two arguments test command, -a file and -e file are the same. But they have some difference, because -a is also a binary operator.

-e unary is defined by POSIX, but -a unary isn’t. POSIX only defines -a binary (See test POSIX).

POSIX defines three arguments test behaviour:

3 arguments:

  • If $2 is a binary primary, perform the binary test of $1
    and $3.

  • If $1 is ‘!’, negate the two-argument test of $2 and $3.

  • If $1 is ‘(‘ and $3 is ‘)’, perform the unary
    test of $2. On systems that do not support the XSI option, the results are unspecified if $1 is ‘(‘ and $3 is ‘)’.

  • Otherwise, produce unspecified results.

So -a also leads to strange result:

$ [ ! -a . ] && echo true
true

-a is considered as binary operator in context of three arguments. See Bash FAQ question E1.
POSIX also mentions that -a is get from KornShell but was changed later to -e because it makes confusing between -a binary and -a unary.

The -e primary, possessing similar functionality to that provided by
the C shell, was added because it provides the only way for a shell
script to find out if a file exists without trying to open the file.
Since implementations are allowed to add additional file types, a
portable script cannot use:

test -b foo -o -c foo -o -d foo -o -f foo -o -p foo

to find out if foo is an existing file. On historical BSD systems, the
existence of a file could be determined by:

test -f foo -o -d foo

but there was no easy way to determine that an existing file was a
regular file. 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.

-a binary is also marked as obsolescent, because it leads to some ambiguous expression, which has greater than 4 arguments. With these >4 arguments expression, POSIX defines the result is unspecified.

Answered By: cuonglm

The best answer I have found is this one, from a StackOverflow question:

-a is deprecated, thus isn’t listed in the manpage for
/usr/bin/test anymore, but still in the one for bash. Use -e . For
single ‘[‘, the bash builtin behaves the same as the test bash
builtin, which behaves the same as /usr/bin/[ and /usr/bin/test
(the one is a symlink to the other). Note the effect of -a depends
on its position: If it’s at the start, it means file exists. If it’s
in the middle of two expressions, it means logical and.

[ ! -a /path ] && echo exists doesn’t work, as the bash manual
points out that -a is considered a binary operator there, and so the
above isn’t parsed as a negate -a .. but as a if '!' and '/path' is
true
(non-empty). Thus, your script always outputs "-a" (which
actually tests for files), and "! -a" which actually is a binary
and here.

For [[, -a isn’t used as a binary and anymore (&& is used
there), so its unique purpose is to check for a file there (although
being deprecated). So, negation actually does what you expect.

Answered By: jimm-cl
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.