13

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?
1
  • Only the developers would know #2---unless they shared their reasoning to the community. Commented Jul 31, 2014 at 18:48

3 Answers 3

13

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.

1
  • Cool to know! Now it's a bit more clear :)!
    – polym
    Commented Jul 31, 2014 at 19:30
9

.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.

2
  • 3
    On the other hand, given that -a is also a boolean operator (and) in bash, it seems bug prone to add this additional meaning that is completely redundant; I'd guess it is more related to compatibility to some other implementation and/or compatibility with earlier versions that didn't have -e.
    – celtschk
    Commented Jul 31, 2014 at 18:37
  • @celtschk - it is not bugprone, but the shell's parser may be. POSIX specifies the -a and -o booleans for [ test ]. But some shells (like bash) sucked at distinguishing an argument from an operator and the [ test ] results were unreliable. Since then POSIX has required that any compliant shell handle at least 4 arguments within the [ test ] brackets.
    – mikeserv
    Commented Aug 1, 2014 at 1:24
6

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.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .