tags:

views:

4906

answers:

3

Both about "-a" and "-e" options in Bash documentation (http://www.gnu.org/software/bash/manual/bashref.html#Bash-Conditional-Expressions) is said:

-a file
    True if file exists.
-e file
    True if file exists.

Trying to get what the difference is I ran the following script:

resin_dir=/Test/Resin_wheleph/Results

if [ -e ${resin_dir} ] ; then
    echo "-e ";
fi

if [ ! -e ${resin_dir} ] ; then
    echo "! -e";
fi

if [ -a ${resin_dir} ] ; then
    echo "-a";
fi

if [ ! -a ${resin_dir} ] ; then
    echo "! -a";
fi

/Test/Resin_wheleph/Results exists and is a directory. And this is what I get:

-e
-a
! -a

which seems to be a little strange (notice "-a" and "! -a"). But when I use double brackets (e. g. if [[ -e ${resin_dir} ]]) in the similar script it gives reasonable output:

-e
-a

So:

  1. What is a difference between "-a" and "-e" options?
  2. Why "-a" produces a strange result when used inside single brackets?
+4  A: 

I researched, and this is quite hairy:

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

Johannes Schaub - litb
The bash builtin uses -a as -e in unary mode and -a as AND in binary mode. Using [[ ]] invokes the bashs builtin, thats why you got reasonable output with it. The single [ bracket invokes as mentioned the test binary which accepts the unary but does not treat it right (should be used as binary)
flolo
Very few shells actually invoke /bin/test (or /bin/[) when you write 'if [ ... ]'. Once upon a long time ago - yes; nowadays, no. Not even vanilla Bourne shell.
Jonathan Leffler
yeah i think shell builtins always take precedence
Johannes Schaub - litb
'[' isn't a builtin at all for bash. see my note on my answer
JimB
wrong: [js@HOST2 cpp]$ help [[: [ arg... ] This is a synonym for the "test" builtin, but the last argument must be a literal `]', to match the opening `['.
Johannes Schaub - litb
On my system /usr/bin/test and /usr/bin/[ are completely different programs:[wheleph ~]$ ls -l /usr/bin/test /usr/bin/\[-rwxr-xr-x 1 root root 31384 Apr 10 2006 /usr/bin/[-rwxr-xr-x 1 root root 28728 Apr 10 2006 /usr/bin/test
wheleph
wheleph. i don't know why that is :) here they are just symlinks at least
Johannes Schaub - litb
A: 

The double bracket [[ exp ]] is a bash builtin. In bash -a and -e are the same, probably for some backwards compatibility.

The single bracket [ exp ] is an alias for the external command "test". In "test", -a is a logical AND. Although [ nothing AND $STRING ] looks like it should be false, test has some syntax quirks, which is why I recommend using the bash builtin [[ exp ]], which tends to be more sane.

Note: bash really does call /bin/[ when you use "[".

$ [ $UNASIGNED_VAR == "bar" ]
bash: [: ==: unary operator expected

the error shows bash called [. An strace also shows "execve("/usr/bin/[", ..."

JimB
[js@HOST2 cpp]$ alias foobash: alias: foo: not found[js@HOST2 cpp]$ does that mean alias now is a extern command? no, it's still a builtin :)
Johannes Schaub - litb
darn, good one :). Still, strace'ing a bash script reveals "execve("/usr/bin/[",...
JimB
Interesting: on Solaris, using 'truss -o bash.truss bash ...' as in my answer and then using 'grep -n exec bash.truss' shows only line 1 executing bash itself. So that is rather odd. (There also isn't a /usr/bin/[ on Solaris - there is a /usr/bin/test.)
Jonathan Leffler
+1  A: 

The '-a' option to the test operator has one meaning as a unary operator and another as a binary operator. As a binary operator, it is the 'and' connective (and '-o' is the 'or' connective). As a unary operator, it apparently tests for a file's existence.

The autoconf system advises you to avoid using '-a' because it causes confusion; now I see why. Indeed, in portable shell programming, it is best to combine the conditions with '&&' or '||'.

I think @litb is on the right track. When you have '! -a ${resin_dir}', Bash may be interpreting it as "is the string '!' non-empty and is the string in '${resin_dir}' non-empty, to which the answer is yes. The Korn shell has a different view on this, and the Bourne shell yet another view - so stay away from '-a'.

On Solaris 10:

$ bash -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
Bad
$ ksh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
OK
$ sh -c 'x=""; if [ ! -a "$x" ] ; then echo OK ; else echo Bad; fi'
sh: test: argument expected
$
Jonathan Leffler